mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-08 22:45:25 +00:00
feat: enhance ScriptError with source context and remove auto-commenting of untranslated pm commands (#7449)
* feat: enhance ScriptError with source context, code snippets, and navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: remove auto-commenting of untranslated pm commands during import Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: update CodeSnippet styles to use theme colors for error and warning highlights * fix: remove unused SCRIPT_TYPES import from network IPC module * refactor: remove unused functions and clean up source-context utility - Removed `getUnifiedScriptContext`, `getWarningSourceGroups`, and related helper functions from `source-context.js` to streamline the utility. - Updated tests in `source-context.spec.js` to reflect the removal of unused functions, ensuring only relevant tests for `findLineInSource` and `getScriptContext` remain. * refactor: simplify ScriptError component and update styles * refactor: streamline tab management in Script components * refactor: enhance tab management in ScriptError and add testsMetadata handling in prepare-request * refactor: improve error source identification in ScriptError component - Enhanced the logic for determining error source types by introducing separate checks for folder and collection files. - Updated the handling of folder file names to ensure accurate UID retrieval and labeling. - Streamlined the overall structure of the getErrorSourceInfo function for better readability and maintainability. * refactor: improve ScriptError component and enhance styling - Simplified the conditions for displaying the ScriptError component in ResponsePane. - Updated navigation logic in ScriptErrorCard to ensure proper handling of source information. - Adjusted styles in StyledWrapper to allow for visible overflow. - Enhanced ScriptErrorIcon to accept additional class names for better styling flexibility. - Minor layout adjustments in RunnerResults ResponsePane for improved UI consistency. * refactor: simplify test description for untranslated pm commands and consolidate error formatter imports * fixes * refactor: update focusedTab logic in Script components to use collection and folder UIDthe respective collection and folder UID instead of the activeTabUid. * feat: add buildErrorContext utility for enhanced error handling in network IPC - Introduced a new utility function `buildErrorContext` to construct detailed error context from script errors. - This function parses error locations, adjusts line numbers, and retrieves relevant source context, improving error reporting. - Removed the previous inline error handling logic from the network IPC module to streamline the codebase. * feat: added playwright test cases to test scriptError behavior * refactor: enhance ScriptError component and improve error handling - Updated labels for error source types in ScriptError to be more concise (e.g., "Request Script" to "Request"). - Improved navigation logic in ScriptErrorCard to ensure proper handling of navigable file paths. - Enhanced styling in StyledWrapper for better visual consistency and user experience. - Added tests to verify fallback behavior for missing error types and non-navigable file paths. - Refined utility functions for better context extraction from script errors. * refactor: normalize file paths for cross-platform compatibility in ScriptError component * refactor: improve file path handling in ScriptError component * refactor: enhance buildErrorContext for improved error handling * docs: add detailed comments to build-error-context for better understanding of error handling * refactor: enhance error block line detection in error formatter - Updated `findScriptBlockEndLine` and `findYmlScriptBlockEndLine` functions to return null for empty or missing blocks, improving accuracy in line detection. - Added comprehensive tests for both functions to ensure correct behavior across various scenarios, including handling of non-.bru and non-.yml files. * refactor: improve error handling and testing in ScriptError and buildErrorContext - Updated ScriptError component to streamline tab management by replacing focusTab with addTab for better request handling. - Enhanced buildErrorContext to return null for empty or missing script blocks in .bru and .yml files, ensuring accurate error reporting. - Added tests to validate behavior for empty script blocks and improved error context extraction in various scenarios. * test: add new tests for script error navigation and handling - Implemented tests for post-response file-path navigation to the Script tab and verification of active sub-tabs. - Added keyboard navigation tests to trigger file-path navigation using the Enter key. - Included a test for multiple error cards to ensure closing one does not affect others. - Enhanced runner tests to verify navigation to the Tests tab from script error results. * refactor: enhance CodeSnippet line rendering and remove unused source-context utilities * refactor: update locators in script-errors tests for improved readability and maintainability * test: enhance script-errors tests to verify error line content * review fixes * refactor: update RunnerResults component and enhance locators for improved testability * refactor: remove buildErrorContext and replace with formatErrorWithContextV2 - Deleted the buildErrorContext function and its associated tests. - Updated network IPC to utilize formatErrorWithContextV2 for improved error context handling. - Enhanced error reporting by ensuring structured error context is returned for desktop UI. * refactor: enhance tab components with data-testid attributes for improved testability - Added data-testid attributes to tab elements in CollectionSettings and FolderSettings components for better integration with testing frameworks. - Updated Tabs and ResponsiveTabs components to include data-testid attributes for tab triggers, enhancing the ability to select and verify tabs in tests. - Modified script-errors tests to utilize new locators for improved readability and maintainability. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "collection-script-error",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: collection-script-error
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const validLine = 1;
|
||||
collectionUndefinedVar.doSomething();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: simple-request
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8081/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "script-errors-test",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
meta {
|
||||
name: script-errors-test
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: folder-request
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8081/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: error-subfolder
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const folderData = "hello";
|
||||
folderUndefinedVar.method();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
meta {
|
||||
name: multiple-errors
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8081/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
console.log("this works fine");
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
postResponseMissingVar();
|
||||
}
|
||||
|
||||
tests {
|
||||
testMissingVar();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: post-response-type-error
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8081/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
const data = res.body;
|
||||
const result = null;
|
||||
result.nonExistentMethod();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: pre-request-ref-error
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8081/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const data = "hello";
|
||||
const result = undefinedVariable + data;
|
||||
console.log(result);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: test-script-error
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8081/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
tests {
|
||||
test("should pass", function() { expect(true).to.equal(true); });
|
||||
nonExistentFunction();
|
||||
}
|
||||
12
tests/script-errors/init-user-data/preferences.json
Normal file
12
tests/script-errors/init-user-data/preferences.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"lastOpenedCollections": [
|
||||
"{{collectionPath}}/script-errors-test",
|
||||
"{{collectionPath}}/collection-script-error"
|
||||
],
|
||||
"preferences": {
|
||||
"onboarding": {
|
||||
"hasLaunchedBefore": true,
|
||||
"hasSeenWelcomeModal": true
|
||||
}
|
||||
}
|
||||
}
|
||||
456
tests/script-errors/script-errors.spec.ts
Normal file
456
tests/script-errors/script-errors.spec.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
import { test, expect, Page } from '../../playwright';
|
||||
import { buildScriptErrorLocators, buildCommonLocators } from '../utils/page/locators';
|
||||
import { openRequest, closeAllTabs } from '../utils/page/actions';
|
||||
import { setSandboxMode, runCollection } from '../utils/page/runner';
|
||||
|
||||
/**
|
||||
* Helper: click send and wait for at least one error card to appear.
|
||||
*/
|
||||
const sendAndWaitForErrorCard = async (page: Page) => {
|
||||
const { request } = buildCommonLocators(page);
|
||||
const scriptErrorLocators = buildScriptErrorLocators(page);
|
||||
await request.sendButton().click();
|
||||
await scriptErrorLocators.card().waitFor({ state: 'visible', timeout: 15000 });
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper: click send and wait for a response status code to appear.
|
||||
* Used for requests that succeed at HTTP level but may have post-response/test errors.
|
||||
*/
|
||||
const sendAndWaitForResponse = async (page: Page) => {
|
||||
const { request, response } = buildCommonLocators(page);
|
||||
await request.sendButton().click();
|
||||
await response.statusCode().waitFor({ state: 'visible', timeout: 15000 });
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper: expand a folder in the sidebar and open a nested request.
|
||||
* Clicking the collection row is idempotent (only expands, never collapses).
|
||||
* Clicking the folder row is idempotent (only expands, never collapses).
|
||||
*/
|
||||
const openFolderRequest = async (page: Page, collectionName: string, folderName: string, requestName: string) => {
|
||||
await test.step(`Open folder request "${requestName}" in "${folderName}"`, async () => {
|
||||
const { sidebar, tabs } = buildCommonLocators(page);
|
||||
await sidebar.collectionRow(collectionName).click();
|
||||
const folder = sidebar.folder(folderName);
|
||||
await folder.waitFor({ state: 'visible' });
|
||||
await folder.click();
|
||||
const request = sidebar.request(requestName);
|
||||
await request.waitFor({ state: 'visible' });
|
||||
await request.click();
|
||||
await expect(tabs.activeRequestTab()).toContainText(requestName);
|
||||
});
|
||||
};
|
||||
|
||||
for (const mode of ['safe', 'developer'] as const) {
|
||||
test.describe.serial(`Script Error Display [${mode} mode]`, () => {
|
||||
let scriptErrorLocators: ReturnType<typeof buildScriptErrorLocators>;
|
||||
let commonLocators: ReturnType<typeof buildCommonLocators>;
|
||||
|
||||
test.beforeAll(async ({ pageWithUserData: page }) => {
|
||||
scriptErrorLocators = buildScriptErrorLocators(page);
|
||||
commonLocators = buildCommonLocators(page);
|
||||
|
||||
await setSandboxMode(page, 'script-errors-test', mode);
|
||||
await setSandboxMode(page, 'collection-script-error', mode);
|
||||
});
|
||||
|
||||
test('1. Pre-request ReferenceError shows error card with correct details', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'pre-request-ref-error');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Verify error card content', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Pre-Request Script Error');
|
||||
await expect(scriptErrorLocators.sourceLabel(card)).toContainText('Request');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('pre-request-ref-error.bru');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('ReferenceError');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('undefinedVariable');
|
||||
await expect(scriptErrorLocators.codeSnippet(card)).toBeVisible();
|
||||
await expect(scriptErrorLocators.errorLine(card)).toBeVisible();
|
||||
await expect(scriptErrorLocators.errorLine(card)).toContainText('undefinedVariable');
|
||||
});
|
||||
|
||||
await test.step('Verify response status shows Error', async () => {
|
||||
await expect(commonLocators.response.statusCode()).toContainText('Error');
|
||||
});
|
||||
});
|
||||
|
||||
test('2. Post-response TypeError shows error card with HTTP 200', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'post-response-type-error');
|
||||
await sendAndWaitForResponse(page);
|
||||
});
|
||||
|
||||
await test.step('Verify error card content', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(card).toBeVisible();
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Post-Response Script Error');
|
||||
await expect(scriptErrorLocators.sourceLabel(card)).toContainText('Request');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('post-response-type-error.bru');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('TypeError');
|
||||
await expect(scriptErrorLocators.errorLine(card)).toContainText('result.nonExistentMethod()');
|
||||
});
|
||||
|
||||
await test.step('Verify HTTP 200 status', async () => {
|
||||
await expect(commonLocators.response.statusCode()).toContainText('200');
|
||||
});
|
||||
});
|
||||
|
||||
test('3. Test script ReferenceError shows error card', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'test-script-error');
|
||||
await sendAndWaitForResponse(page);
|
||||
});
|
||||
|
||||
await test.step('Verify error card content', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(card).toBeVisible();
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Test Script Error');
|
||||
await expect(scriptErrorLocators.sourceLabel(card)).toContainText('Request');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('test-script-error.bru');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('ReferenceError');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('nonExistentFunction');
|
||||
await expect(scriptErrorLocators.errorLine(card)).toContainText('nonExistentFunction()');
|
||||
});
|
||||
});
|
||||
|
||||
test('4. Stack trace toggle shows and hides stack trace', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'pre-request-ref-error');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Verify stack toggle is visible and stack is hidden', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(scriptErrorLocators.stackToggle(card)).toBeVisible();
|
||||
await expect(scriptErrorLocators.stackToggle(card)).toContainText('Show stack trace');
|
||||
await expect(scriptErrorLocators.stack(card)).not.toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Click toggle to show stack trace', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.stackToggle(card).click();
|
||||
await expect(scriptErrorLocators.stack(card)).toBeVisible();
|
||||
await expect(scriptErrorLocators.stackToggle(card)).toContainText('Hide stack trace');
|
||||
});
|
||||
|
||||
await test.step('Click toggle to hide stack trace again', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.stackToggle(card).click();
|
||||
await expect(scriptErrorLocators.stack(card)).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('5. Close button hides card and ScriptErrorIcon restores it', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'pre-request-ref-error');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Close error card', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(card).toBeVisible();
|
||||
await scriptErrorLocators.closeButton(card).click();
|
||||
await expect(scriptErrorLocators.cards()).toHaveCount(0);
|
||||
});
|
||||
|
||||
await test.step('Click error icon to restore card', async () => {
|
||||
await expect(scriptErrorLocators.errorIcon()).toBeVisible();
|
||||
await scriptErrorLocators.errorIcon().click();
|
||||
await expect(scriptErrorLocators.card()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('6. Multiple error cards for post-response and test failures', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'multiple-errors');
|
||||
await sendAndWaitForResponse(page);
|
||||
});
|
||||
|
||||
await test.step('Verify two error cards are displayed', async () => {
|
||||
await expect(scriptErrorLocators.cards()).toHaveCount(2);
|
||||
});
|
||||
|
||||
await test.step('Verify first card is post-response error', async () => {
|
||||
const card0 = scriptErrorLocators.card(0);
|
||||
await expect(scriptErrorLocators.title(card0)).toContainText('Post-Response Script Error');
|
||||
await expect(scriptErrorLocators.message(card0)).toContainText('postResponseMissingVar');
|
||||
await expect(scriptErrorLocators.errorLine(card0)).toContainText('postResponseMissingVar()');
|
||||
});
|
||||
|
||||
await test.step('Verify second card is test script error', async () => {
|
||||
const card1 = scriptErrorLocators.card(1);
|
||||
await expect(scriptErrorLocators.title(card1)).toContainText('Test Script Error');
|
||||
await expect(scriptErrorLocators.message(card1)).toContainText('testMissingVar');
|
||||
await expect(scriptErrorLocators.errorLine(card1)).toContainText('testMissingVar()');
|
||||
});
|
||||
|
||||
await test.step('Verify HTTP 200 status', async () => {
|
||||
await expect(commonLocators.response.statusCode()).toContainText('200');
|
||||
});
|
||||
});
|
||||
|
||||
test('7. Folder-level script error shows folder source label', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open folder request', async () => {
|
||||
await openFolderRequest(page, 'script-errors-test', 'error-subfolder', 'folder-request');
|
||||
});
|
||||
|
||||
await test.step('Send request and wait for error', async () => {
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Verify folder-level error card', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Pre-Request Script Error');
|
||||
await expect(scriptErrorLocators.sourceLabel(card)).toContainText('Folder');
|
||||
await expect(scriptErrorLocators.sourceLabel(card)).toContainText('error-subfolder');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('folder.bru');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('ReferenceError');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('folderUndefinedVar');
|
||||
await expect(scriptErrorLocators.errorLine(card)).toContainText('folderUndefinedVar');
|
||||
});
|
||||
});
|
||||
|
||||
test('8. Folder file-path navigation opens folder settings', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open folder request and trigger error', async () => {
|
||||
// Folder was expanded by test 7 (serial), so openRequest can find the nested request
|
||||
await openRequest(page, 'script-errors-test', 'folder-request');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify navigation to folder settings with Script tab', async () => {
|
||||
const activeTab = commonLocators.tabs.activeRequestTab();
|
||||
await expect(activeTab).toContainText('error-subfolder');
|
||||
const scriptTab = commonLocators.paneTabs.folderSettingsTab('script');
|
||||
await expect(scriptTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test('9. Collection-level script error shows collection source label', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request in collection-script-error', async () => {
|
||||
await openRequest(page, 'collection-script-error', 'simple-request');
|
||||
});
|
||||
|
||||
await test.step('Send request and wait for error', async () => {
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Verify collection-level error card', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Pre-Request Script Error');
|
||||
await expect(scriptErrorLocators.sourceLabel(card)).toContainText('Collection');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('collection.bru');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('ReferenceError');
|
||||
await expect(scriptErrorLocators.message(card)).toContainText('collectionUndefinedVar');
|
||||
await expect(scriptErrorLocators.errorLine(card)).toContainText('collectionUndefinedVar');
|
||||
});
|
||||
});
|
||||
|
||||
test('10. Collection file-path navigation opens collection settings', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and trigger collection error', async () => {
|
||||
await openRequest(page, 'collection-script-error', 'simple-request');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify navigation to collection settings with Script tab', async () => {
|
||||
const activeTab = commonLocators.tabs.activeRequestTab();
|
||||
await expect(activeTab).toContainText('Collection');
|
||||
const scriptTab = commonLocators.paneTabs.collectionSettingsTab('script');
|
||||
await expect(scriptTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test('11. Request file-path navigation opens Script tab for pre-request error', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and trigger error', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'pre-request-ref-error');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify Script pane tab is active', async () => {
|
||||
const activeTab = commonLocators.tabs.activeRequestTab();
|
||||
await expect(activeTab).toContainText('pre-request-ref-error');
|
||||
const scriptTab = commonLocators.paneTabs.responsiveTab('script');
|
||||
await expect(scriptTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test('12. Request file-path navigation opens Tests tab for test error', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and trigger error', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'test-script-error');
|
||||
await sendAndWaitForResponse(page);
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(card).toBeVisible();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify Tests pane tab is active', async () => {
|
||||
const testsTab = commonLocators.paneTabs.responsiveTab('tests');
|
||||
await expect(testsTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test('13. Runner: clicking request error file path opens request tab', async ({ pageWithUserData: page }) => {
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
|
||||
await test.step('Close all existing request tabs', async () => {
|
||||
await closeAllTabs(page);
|
||||
});
|
||||
|
||||
await test.step('Run collection via runner', async () => {
|
||||
await runCollection(page, 'script-errors-test');
|
||||
});
|
||||
|
||||
await test.step('Click on failed request result to open detail pane', async () => {
|
||||
const resultItem = commonLocators.runnerResults.itemPath('pre-request-ref-error');
|
||||
await resultItem.locator('.danger').filter({ hasText: '(request failed)' }).click();
|
||||
});
|
||||
|
||||
await test.step('Verify script error card in runner detail pane', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await card.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Pre-Request Script Error');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('pre-request-ref-error.bru');
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate to request', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify request tab opened with Script sub-tab active', async () => {
|
||||
const activeTab = commonLocators.tabs.activeRequestTab();
|
||||
await expect(activeTab).toContainText('pre-request-ref-error');
|
||||
const scriptTab = commonLocators.paneTabs.responsiveTab('script');
|
||||
await expect(scriptTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test('14. Post-response file-path navigation opens Script tab with Post Response sub-tab', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and trigger post-response error', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'post-response-type-error');
|
||||
await sendAndWaitForResponse(page);
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await expect(card).toBeVisible();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify Script pane tab is active', async () => {
|
||||
const scriptTab = commonLocators.paneTabs.responsiveTab('script');
|
||||
await expect(scriptTab).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
await test.step('Verify Post Response sub-tab is active', async () => {
|
||||
const postResponseSubTab = commonLocators.paneTabs.tabTrigger('post-response');
|
||||
await expect(postResponseSubTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test('15. Keyboard navigation (Enter key) triggers file-path navigation', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and trigger error', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'pre-request-ref-error');
|
||||
await sendAndWaitForErrorCard(page);
|
||||
});
|
||||
|
||||
await test.step('Focus file path and press Enter', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.filePath(card).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
});
|
||||
|
||||
await test.step('Verify Script pane tab is active (same as click navigation)', async () => {
|
||||
const activeTab = commonLocators.tabs.activeRequestTab();
|
||||
await expect(activeTab).toContainText('pre-request-ref-error');
|
||||
const scriptTab = commonLocators.paneTabs.responsiveTab('script');
|
||||
await expect(scriptTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
// Skip: currently closing one error card closes all cards. Unskip once independent card close is implemented.
|
||||
test.skip('16. Multiple error cards — closing one preserves the other', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open request and send', async () => {
|
||||
await openRequest(page, 'script-errors-test', 'multiple-errors');
|
||||
await sendAndWaitForResponse(page);
|
||||
});
|
||||
|
||||
await test.step('Verify two error cards exist', async () => {
|
||||
await expect(scriptErrorLocators.cards()).toHaveCount(2);
|
||||
});
|
||||
|
||||
await test.step('Close the first card (post-response error)', async () => {
|
||||
const card0 = scriptErrorLocators.card(0);
|
||||
await scriptErrorLocators.closeButton(card0).click();
|
||||
});
|
||||
|
||||
await test.step('Verify only one card remains and it is the test script error', async () => {
|
||||
await expect(scriptErrorLocators.cards()).toHaveCount(1);
|
||||
const remainingCard = scriptErrorLocators.card(0);
|
||||
await expect(scriptErrorLocators.title(remainingCard)).toContainText('Test Script Error');
|
||||
await expect(scriptErrorLocators.message(remainingCard)).toContainText('testMissingVar');
|
||||
});
|
||||
|
||||
await test.step('Verify ScriptErrorIcon appears for the closed card', async () => {
|
||||
await expect(scriptErrorLocators.errorIcon()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('17. Runner: test error file-path navigation opens Tests tab', async ({ pageWithUserData: page }) => {
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
|
||||
await test.step('Close all existing request tabs', async () => {
|
||||
await closeAllTabs(page);
|
||||
});
|
||||
|
||||
await test.step('Run collection via runner', async () => {
|
||||
await runCollection(page, 'script-errors-test');
|
||||
});
|
||||
|
||||
await test.step('Click on test-script-error result to open detail pane', async () => {
|
||||
const resultItem = commonLocators.runnerResults.itemPath('test-script-error');
|
||||
await resultItem.locator('.link').click();
|
||||
});
|
||||
|
||||
await test.step('Verify script error card in runner detail pane', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await card.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await expect(scriptErrorLocators.title(card)).toContainText('Test Script Error');
|
||||
await expect(scriptErrorLocators.filePath(card)).toContainText('test-script-error.bru');
|
||||
});
|
||||
|
||||
await test.step('Click file path to navigate to request', async () => {
|
||||
const card = scriptErrorLocators.card();
|
||||
await scriptErrorLocators.filePath(card).click();
|
||||
});
|
||||
|
||||
await test.step('Verify request tab opened with Tests sub-tab active', async () => {
|
||||
const activeTab = commonLocators.tabs.activeRequestTab();
|
||||
await expect(activeTab).toContainText('test-script-error');
|
||||
const testsTab = commonLocators.paneTabs.responsiveTab('tests');
|
||||
await expect(testsTab).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from '../../../playwright';
|
||||
import { Page, Locator } from '../../../playwright';
|
||||
|
||||
export const buildCommonLocators = (page: Page) => ({
|
||||
runner: () => page.getByTestId('run-button'),
|
||||
@@ -18,9 +18,7 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
return folderWrapper.locator('.collection-item-name').filter({ hasText: requestName });
|
||||
},
|
||||
closeAllCollectionsButton: () => page.getByTestId('collections-header-actions-menu-close-all'),
|
||||
collectionRow: (name: string) => page.locator('.collection-name').filter({
|
||||
has: page.locator('#sidebar-collection-name', { hasText: name })
|
||||
})
|
||||
collectionRow: (name: string) => page.getByTestId('sidebar-collection-row').filter({ hasText: name })
|
||||
},
|
||||
actions: {
|
||||
collectionActions: (collectionName: string) =>
|
||||
@@ -41,6 +39,12 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
activeRequestTab: () => page.locator('.request-tab.active'),
|
||||
closeTab: (requestName: string) => page.locator('.request-tab').filter({ hasText: requestName }).getByTestId('request-tab-close-icon')
|
||||
},
|
||||
paneTabs: {
|
||||
responsiveTab: (key: string) => page.getByTestId(`responsive-tab-${key}`),
|
||||
collectionSettingsTab: (key: string) => page.getByTestId(`collection-settings-tab-${key}`),
|
||||
folderSettingsTab: (key: string) => page.getByTestId(`folder-settings-tab-${key}`),
|
||||
tabTrigger: (key: string) => page.getByTestId(`tab-trigger-${key}`)
|
||||
},
|
||||
folder: {
|
||||
chevron: (folderName: string) => page.locator('.collection-item-name').filter({ hasText: folderName }).getByTestId('folder-chevron')
|
||||
},
|
||||
@@ -83,6 +87,9 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
input: () => page.getByTestId('tag-input').getByRole('textbox'),
|
||||
item: (tagName: string) => page.locator('.tag-item', { hasText: tagName })
|
||||
},
|
||||
runnerResults: {
|
||||
itemPath: (name: string) => page.getByTestId('runner-result-item').filter({ hasText: name })
|
||||
},
|
||||
response: {
|
||||
statusCode: () => page.getByTestId('response-status-code'),
|
||||
pane: () => page.locator('.response-pane'),
|
||||
@@ -207,6 +214,41 @@ export const buildGrpcCommonLocators = (page: Page) => ({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds locators for script error display elements
|
||||
* @param page - The Playwright page object
|
||||
* @returns Object with locators for script error elements
|
||||
*/
|
||||
export const buildScriptErrorLocators = (page: Page) => ({
|
||||
/** All error cards on the page */
|
||||
cards: () => page.getByTestId('script-error-card'),
|
||||
/** Nth error card (0-indexed) */
|
||||
card: (index?: number) => {
|
||||
const cards = page.getByTestId('script-error-card');
|
||||
return index !== undefined ? cards.nth(index) : cards.first();
|
||||
},
|
||||
/** Error title within a card */
|
||||
title: (card?: Locator) => (card ?? page).getByTestId('script-error-title'),
|
||||
/** Close button within a card */
|
||||
closeButton: (card?: Locator) => (card ?? page).getByTestId('script-error-close'),
|
||||
/** Source label within a card */
|
||||
sourceLabel: (card?: Locator) => (card ?? page).getByTestId('script-error-source-label'),
|
||||
/** File path link within a card */
|
||||
filePath: (card?: Locator) => (card ?? page).getByTestId('script-error-file-path'),
|
||||
/** Error message within a card */
|
||||
message: (card?: Locator) => (card ?? page).getByTestId('script-error-message'),
|
||||
/** Code snippet within a card */
|
||||
codeSnippet: (card?: Locator) => (card ?? page).getByTestId('code-snippet'),
|
||||
/** Error-highlighted code line within a card */
|
||||
errorLine: (card?: Locator) => (card ?? page).getByTestId('code-line-error'),
|
||||
/** Stack trace toggle within a card */
|
||||
stackToggle: (card?: Locator) => (card ?? page).getByTestId('script-error-stack-toggle'),
|
||||
/** Stack trace content within a card */
|
||||
stack: (card?: Locator) => (card ?? page).getByTestId('script-error-stack'),
|
||||
/** ScriptErrorIcon (the red alert button shown when card is dismissed) */
|
||||
errorIcon: () => page.getByTestId('script-error-icon')
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds locators for sandbox mode settings
|
||||
* @param page - The Playwright page object
|
||||
|
||||
Reference in New Issue
Block a user