diff --git a/packages/html-reporter/src/common.css b/packages/html-reporter/src/common.css index bf1926a6cc733..dc3323d29ffb9 100644 --- a/packages/html-reporter/src/common.css +++ b/packages/html-reporter/src/common.css @@ -208,6 +208,7 @@ article, aside, details, figcaption, figure, footer, header, main, menu, nav, se line-height: 20px; color: var(--color-fg-default); border: 1px solid var(--color-border-default); + user-select: none; } .subnav-item:hover { diff --git a/packages/html-reporter/src/headerView.spec.tsx b/packages/html-reporter/src/headerView.spec.tsx index 1ad79683ab18b..85e78758b01ee 100644 --- a/packages/html-reporter/src/headerView.spec.tsx +++ b/packages/html-reporter/src/headerView.spec.tsx @@ -39,7 +39,10 @@ test('should render counters', async ({ mount }) => { await expect(component).toMatchAriaSnapshot(` - navigation: - link "All90" - - text: Passed42 Failed31 Flaky17 Skipped10 + - link "Passed42" + - link "Failed31" + - link "Flaky17" + - link "Skipped10" `); }); diff --git a/packages/html-reporter/src/headerView.tsx b/packages/html-reporter/src/headerView.tsx index f44b4daf87ca6..d385c12992069 100644 --- a/packages/html-reporter/src/headerView.tsx +++ b/packages/html-reporter/src/headerView.tsx @@ -81,31 +81,34 @@ export const GlobalFilterView: React.FC<{ const StatsNavView: React.FC<{ stats: Stats }> = ({ stats }) => { - const searchParams = React.useContext(SearchParamsContext); - const q = searchParams.get('q')?.toString() || ''; return ; }; + +const NavLink: React.FC<{ + token: string, + count: number, +}> = ({ token, count }) => { + const searchParams = React.useContext(SearchParamsContext); + const q = searchParams.get('q')?.toString() || ''; + const queryToken = `s:${token}`; + + const clickUrl = filterWithQuery(q, queryToken, false); + const ctrlClickUrl = filterWithQuery(q, queryToken, true); + + const label = token.charAt(0).toUpperCase() + token.slice(1); + + return + {count > 0 && statusIcon(token as any)} + {label} + {count} + ; +}; diff --git a/packages/html-reporter/src/links.css b/packages/html-reporter/src/links.css index 29dc2fc0c6892..d8f7380676cf6 100644 --- a/packages/html-reporter/src/links.css +++ b/packages/html-reporter/src/links.css @@ -115,6 +115,7 @@ flex: none; background-color: transparent; border-color: transparent; + user-select: none; } .link-badge-dim span { diff --git a/packages/html-reporter/src/testFileView.css b/packages/html-reporter/src/testFileView.css index 4c2b11abae955..006dda1042968 100644 --- a/packages/html-reporter/src/testFileView.css +++ b/packages/html-reporter/src/testFileView.css @@ -17,7 +17,7 @@ .test-file-test { line-height: 32px; align-items: center; - padding: 2px 10px; + padding: 2px 8px; overflow: hidden; text-overflow: ellipsis; } diff --git a/packages/injected/src/recorder/recorder.ts b/packages/injected/src/recorder/recorder.ts index 7e20c8c78a551..4d62f68c7fc10 100644 --- a/packages/injected/src/recorder/recorder.ts +++ b/packages/injected/src/recorder/recorder.ts @@ -422,9 +422,7 @@ class RecordActionTool implements RecorderTool { if (target.nodeName === 'SELECT') { const selectElement = target as HTMLSelectElement; - if (this._actionInProgress(event)) - return; - this._performAction({ + this._recordAction({ name: 'select', selector: this._activeModel!.selector, options: [...selectElement.selectedOptions].map(option => option.value), @@ -642,6 +640,15 @@ class JsonRecordActionTool implements RecorderTool { this._recorder = recorder; } + install() { + // No highlight for the lightweight recorder. + this._recorder.highlight.uninstall(); + } + + uninstall() { + this._recorder.highlight.install(); + } + onClick(event: MouseEvent) { // in webkit, sliding a range element may trigger a click event with a different target if the mouse is released outside the element bounding box. // So we check the hovered element instead, and if it is a range input, we skip click handling diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index c470fe5f23dea..2630357086832 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -52,22 +52,22 @@ export const Recorder: React.FC = ({ React.useEffect(() => { if (!sources.length) return; - const selectedSource = sources.find(s => s.id === selectedFileId); + // When no selected file id present, pick the primary source (target language). + let fileId = selectedFileId ?? sources.find(s => s.isPrimary)?.id; + const selectedSource = sources.find(s => s.id === fileId); const newestSource = sources.sort((a, b) => b.timestamp - a.timestamp)[0]; if (!selectedSource || newestSource.isRecorded !== selectedSource.isRecorded) { - // Debugger kicked in, or recording resumed. Switch selection to the newest source. - setSelectedFileId(newestSource.id); + // When debugger kicks in, or recording is resumed switch the selection to the newest source. + fileId = newestSource.id; } + // If changes above force the selection to change, update the state. + if (fileId !== selectedFileId) + setSelectedFileId(fileId); }, [sources, selectedFileId]); const source = React.useMemo(() => { const source = sources.find(s => s.id === selectedFileId); - if (source) - return source; - const primarySource = sources.find(s => s.isPrimary); - if (primarySource) - return primarySource; - return emptySource(); + return source ?? emptySource(); }, [sources, selectedFileId]); const [locator, setLocator] = React.useState(''); diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts index e7ed9a952653f..57b20f92c1dfb 100644 --- a/tests/library/inspector/inspectorTest.ts +++ b/tests/library/inspector/inspectorTest.ts @@ -28,7 +28,7 @@ export { expect } from '@playwright/test'; type CLITestArgs = { recorderPageGetter: () => Promise; closeRecorder: () => Promise; - openRecorder: (options?: { testIdAttributeName: string }) => Promise<{ recorder: Recorder, page: Page }>; + openRecorder: (options?: { testIdAttributeName?: string, language?: string }) => Promise<{ recorder: Recorder, page: Page }>; runCLI: (args: string[], options?: { autoExitWhen?: string }) => CLIMock; }; @@ -87,7 +87,6 @@ export const test = contextTest.extend({ openRecorder: async ({ context, recorderPageGetter }, use) => { await use(async options => { await (context as any)._enableRecorder({ - language: 'javascript', mode: 'recording', ...options }); diff --git a/tests/library/inspector/title.spec.ts b/tests/library/inspector/title.spec.ts index b4dd50ee36038..a2569e74e6e4d 100644 --- a/tests/library/inspector/title.spec.ts +++ b/tests/library/inspector/title.spec.ts @@ -57,3 +57,9 @@ test('should update primary page URL when original primary closes', async ({ `Playwright Inspector - ${server.PREFIX}/dom.html`, ); }); + +test('should render primary language', async ({ openRecorder }) => { + const { recorder } = await openRecorder({ language: 'python' }); + await recorder.setContentAndWait(''); + await expect(recorder.recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue('python'); +});