From b4a646a624c0b1e8e352d320cbc6684581625ff6 Mon Sep 17 00:00:00 2001 From: Denis Skvortsov Date: Thu, 6 Aug 2026 13:42:56 +0200 Subject: [PATCH 1/3] fix(html-reporter): use a button element for the chip header (#42149) --- packages/html-reporter/src/chip.css | 10 ++++++++++ packages/html-reporter/src/chip.spec.ts | 19 +++++++++++++++++- packages/html-reporter/src/chip.story.tsx | 3 +++ packages/html-reporter/src/chip.tsx | 22 +++++++++++++-------- tests/playwright-test/reporter-html.spec.ts | 4 ++-- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/packages/html-reporter/src/chip.css b/packages/html-reporter/src/chip.css index 88137cdbcd96f..f37aff953b642 100644 --- a/packages/html-reporter/src/chip.css +++ b/packages/html-reporter/src/chip.css @@ -15,6 +15,8 @@ */ .chip-header { + display: block; + width: 100%; border: 1px solid var(--color-border-default); border-top-left-radius: 6px; border-top-right-radius: 6px; @@ -22,14 +24,22 @@ padding: 0 8px; border-bottom: none; margin-top: 12px; + color: inherit; + font: inherit; font-weight: 600; line-height: 38px; + text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; user-select: none; } +.chip-header:focus-visible { + outline: 1px solid var(--color-accent-fg); + outline-offset: -1px; +} + .chip-header-allow-selection { user-select: text; } diff --git a/packages/html-reporter/src/chip.spec.ts b/packages/html-reporter/src/chip.spec.ts index 291654cefa038..69d6444dbfb57 100644 --- a/packages/html-reporter/src/chip.spec.ts +++ b/packages/html-reporter/src/chip.spec.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; -import type { Auto, AutoCollapsed, Stateful, WithBody } from './chip.story'; +import type { Auto, AutoCollapsed, NotExpandable, Stateful, WithBody } from './chip.story'; test.use({ viewport: { width: 500, height: 500 } }); @@ -50,6 +50,23 @@ test('body render prop is rendered', async ({ mount }) => { await expect(component.getByText('Chip children')).toBeVisible(); }); +test('chip without setExpanded is not a button', async ({ mount }) => { + const component = await mount('chip/NotExpandable'); + await expect(component.getByRole('button')).toHaveCount(0); + await expect(component.getByText('Body')).toBeVisible(); +}); + +test('expand collapse with the keyboard', async ({ mount, page }) => { + const component = await mount('chip/AutoCollapsed'); + const header = component.getByRole('button', { name: 'Title' }); + await header.focus(); + await expect(header).toBeFocused(); + await page.keyboard.press('Enter'); + await expect(component.getByText('Body')).toBeVisible(); + await page.keyboard.press('Space'); + await expect(component.getByText('Body')).not.toBeVisible(); +}); + test('setExpanded should work', async ({ mount }) => { const component = await mount('chip/AutoCollapsed'); await component.getByText('Title').click(); diff --git a/packages/html-reporter/src/chip.story.tsx b/packages/html-reporter/src/chip.story.tsx index 5945cfdb459ca..48fd0b0c93e19 100644 --- a/packages/html-reporter/src/chip.story.tsx +++ b/packages/html-reporter/src/chip.story.tsx @@ -33,3 +33,6 @@ export const Stateful = () => { export const WithBody = () => 'Body from render prop'}>Chip children; + +export const NotExpandable = () => + Body; diff --git a/packages/html-reporter/src/chip.tsx b/packages/html-reporter/src/chip.tsx index f0dce3e48f47e..1a6926e36c28b 100644 --- a/packages/html-reporter/src/chip.tsx +++ b/packages/html-reporter/src/chip.tsx @@ -33,17 +33,23 @@ export const Chip: React.FC<{ dataTestId?: string, }> = ({ header, footer, expanded, setExpanded, children, noInsets, body, dataTestId }) => { const id = React.useId(); + const title = typeof header === 'string' ? header : undefined; + const headerContent = <> + {setExpanded ? (expanded ? : ) : } + {header} + ; return
-
setExpanded?.(!expanded)} - title={typeof header === 'string' ? header : undefined}> - {setExpanded ? (expanded ? : ) : } - {header} -
+ className={clsx('chip-header', 'expanded-' + expanded)} + onClick={() => setExpanded(!expanded)} + title={title}> + {headerContent} + :
+ {headerContent} +
} {(!setExpanded || expanded) &&
{children} {body && body()} diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 707c4c04b25e1..9ce7ac74a2569 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -3431,7 +3431,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(page.getByRole('link', { name: 'Speedboard' })).toHaveAttribute('aria-selected', 'true'); await expect(page).toMatchAriaSnapshot(` - - button "Slowest Tests" + - text: /Slowest Tests/ - region: - list: - listitem: @@ -3455,7 +3455,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { `); await page.getByText('foo').first().click(); await expect(page).toMatchAriaSnapshot(` - - button "Slowest Tests" + - text: /Slowest Tests/ `); await page.getByRole('link', { name: 'Failed' }).click(); From f1fc9fcae9005ec877ac984f99ac43097b213a87 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 6 Aug 2026 17:53:47 +0200 Subject: [PATCH 2/3] fix(cli): hide Windows daemon consoles (#42155) --- packages/playwright-core/src/tools/backend/devtools.ts | 2 +- packages/playwright-core/src/tools/cli-client/program.ts | 1 + packages/playwright-core/src/tools/cli-client/session.ts | 1 + packages/utils/processLauncher.ts | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/tools/backend/devtools.ts b/packages/playwright-core/src/tools/backend/devtools.ts index 56fe53258ef88..ee35a90b20ddb 100644 --- a/packages/playwright-core/src/tools/backend/devtools.ts +++ b/packages/playwright-core/src/tools/backend/devtools.ts @@ -135,7 +135,7 @@ const annotate = defineTabTool({ const daemonArgs = [daemonScript, `--pageId=${pageId}`]; // Spawn the dashboard daemon (idempotent — the singleton socket guards against duplicates). - const daemon = spawn(process.execPath, daemonArgs, { detached: true, stdio: 'ignore' }); + const daemon = spawn(process.execPath, daemonArgs, { detached: true, stdio: 'ignore', windowsHide: true }); daemon.unref(); // Spawn the annotate client in JSON mode to capture the raw payload over stdout. diff --git a/packages/playwright-core/src/tools/cli-client/program.ts b/packages/playwright-core/src/tools/cli-client/program.ts index ef0e82a34fb8b..3ac0efd0417f4 100644 --- a/packages/playwright-core/src/tools/cli-client/program.ts +++ b/packages/playwright-core/src/tools/cli-client/program.ts @@ -248,6 +248,7 @@ export async function program(options?: { embedderVersion?: string}) { const child = spawn(process.execPath, daemonArgs, { detached: !foreground, stdio: foreground ? 'inherit' : ['pipe', 'pipe', 'ignore'], + windowsHide: true, }); if (foreground) { await new Promise(resolve => child.on('exit', () => resolve())); diff --git a/packages/playwright-core/src/tools/cli-client/session.ts b/packages/playwright-core/src/tools/cli-client/session.ts index 2db6a105e222b..290d8cbff8c14 100644 --- a/packages/playwright-core/src/tools/cli-client/session.ts +++ b/packages/playwright-core/src/tools/cli-client/session.ts @@ -148,6 +148,7 @@ export class Session { detached: true, stdio: ['ignore', 'pipe', err], cwd: process.cwd(), // Will be used as root. + windowsHide: true, }); let signalled = false; diff --git a/packages/utils/processLauncher.ts b/packages/utils/processLauncher.ts index 9c77c7a0ce3ad..f865dda5aae60 100644 --- a/packages/utils/processLauncher.ts +++ b/packages/utils/processLauncher.ts @@ -233,7 +233,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise Date: Thu, 6 Aug 2026 10:07:46 -0700 Subject: [PATCH 3/3] devops: pass npm dist-tag to ESRP via productstate (#42147) --- .azure-pipelines/publish.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.azure-pipelines/publish.yml b/.azure-pipelines/publish.yml index 9084641a45646..9c2fa39de55de 100644 --- a/.azure-pipelines/publish.yml +++ b/.azure-pipelines/publish.yml @@ -99,15 +99,6 @@ extends: set -e mkdir -p "$(Build.ArtifactStagingDirectory)/esrp-build" node utils/workspace.js --list-public-package-paths | while read package; do - # ESRP runs `npm publish` without a --tag argument, so the dist-tag - # has to be baked into each tarball via publishConfig.tag. - node -e " - const fs = require('fs'); - const file = process.argv[1] + '/package.json'; - const pkg = JSON.parse(fs.readFileSync(file, 'utf8')); - pkg.publishConfig = { ...pkg.publishConfig, tag: 'next' }; - fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + '\n'); - " "$package" npm pack --pack-destination="$(Build.ArtifactStagingDirectory)/esrp-build" "$package" done ls -la "$(Build.ArtifactStagingDirectory)/esrp-build" @@ -133,6 +124,8 @@ extends: clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5' intent: 'PackageDistribution' contenttype: 'npm' + # npm dist-tag to publish with. + productstate: 'next' folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build' waitforreleasecompletion: true owners: 'yurys@microsoft.com'