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');
+});