Skip to content

Commit 74318a1

Browse files
authored
fix(tools): migrate unit test runner from playwright to puppeteer (#3121)
* fix(tools): migrate unit test runner from playwright to puppeteer Playwright 1.57.0 removed `page.accessibility.snapshot()`, breaking all a11y tree snapshot tests. Puppeteer still supports this API. Replaced `@web/test-runner-playwright` with `@web/test-runner-chrome` plus `puppeteer` in pfe-tools peer dependencies. BREAKING CHANGE: downstream consumers must install `puppeteer` and `@web/test-runner-chrome` instead of `@web/test-runner-playwright`. Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: migrate a11y snapshot tests to ax helper assertions Replace raw snapshot property access (deep.equal, children?.find, children?.filter) with ax helper assertions (axContainRole, axContainQuery, axContainName, axTreeFocusedNode, querySnapshot, querySnapshotAll). This makes tests resilient to snapshot format differences between browser automation backends (Puppeteer includes extra properties like backendNodeId, loaderId that Playwright did not). Also fixes chai `.not` flag bleeding through `.and` chaining in combobox-controller tests by splitting into separate expect() calls. Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: lint * fix(tools): add --no-sandbox for CI environments Puppeteer's Chrome requires --no-sandbox on GitHub Actions runners where unprivileged user namespaces are restricted by AppArmor. Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): update playwright docker image and peerDep to v1.57 The SSR test container used playwright v1.48.2 but the lockfile resolved playwright v1.57.0, causing browser binary mismatches. Aligns `@playwright/test` peerDep with `playwright` at ~1.57.0. Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(hint): migrate to ax helper assertions Replace raw snapshot destructuring and role string comparisons with ax helpers. Puppeteer uses 'StaticText' role instead of 'text', and includes extra properties in snapshot nodes. Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(tools): fix test concurrency and Puppeteer compatibility - Set concurrency to 1: concurrent pages in same browser cause focus contention, making keyboard/mouse tests flaky. This affects both Playwright and Puppeteer. - Add `press()` utility to pfe-tools that decomposes modifier key combos (e.g. Shift+Tab) for Puppeteer compatibility, since Puppeteer does not support combo key names in `keyboard.press()`. - Fix accordion tests: replace `axTreeFocusOn(document.body)` with assertions that don't depend on page-level focus (Puppeteer can't Tab out of the page). Remove Shift+Tab-to-body tests that test browser chrome behavior, not component behavior. - Fix search-input Tab test: assert listbox collapsed instead of checking page-level focus state. Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 41b2ef4 commit 74318a1

26 files changed

Lines changed: 238 additions & 390 deletions

File tree

.changeset/big-dingos-live.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@patternfly/pfe-tools": major
3+
---
4+
5+
**Test Runner**: migrate config from playwright-backed to puppeteer.
6+
7+
Transitive dependencies have changed, so if your test files relied on playwright imports,
8+
you'll need to update them.
9+

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ jobs:
9898
name: SSR Tests (Playwright)
9999
runs-on: ubuntu-latest
100100
container:
101-
image: mcr.microsoft.com/playwright:v1.48.2-noble
101+
image: mcr.microsoft.com/playwright:v1.57.0-noble
102102
steps:
103103
- uses: actions/checkout@v4
104104
- uses: actions/setup-node@v4

core/pfe-core/controllers/internals-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export class InternalsController implements ReactiveController, ARIAMixin {
340340
/** @see https://w3c.github.io/aria/#ref-for-dom-ariamixin-ariaactivedescendantelement-1 */
341341
declare global {
342342
// https://github.com/webcomponents-cg/community-protocols/pull/75
343-
var _elementInternals: WeakMap<Element, ElementInternals>; // eslint-disable-line no-var
343+
var _elementInternals: WeakMap<Element, ElementInternals>;
344344
interface ARIAMixin {
345345
ariaActiveDescendantElement: Element | null;
346346
ariaControlsElements: readonly Element[] | null;

core/pfe-core/controllers/slot-controller-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class SlotController implements SlotControllerPublicAPI {
1212

1313
static anonymousAttribute = 'ssr-hint-has-slotted-default' as const;
1414

15-
constructor(public host: ReactiveElement, ..._: SlotControllerArgs) {
15+
constructor(public host: ReactiveElement, ..._args: SlotControllerArgs) {
1616
host.addController(this);
1717
}
1818

@@ -24,7 +24,7 @@ export class SlotController implements SlotControllerPublicAPI {
2424
.map(x => x.trim());
2525
}
2626

27-
getSlotted<T extends Element = Element>(..._: (string | null)[]): T[] {
27+
getSlotted<T extends Element = Element>(..._names: (string | null)[]): T[] {
2828
return [];
2929
}
3030

core/pfe-core/controllers/test/combobox-controller.spec.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, fixture, nextFrame } from '@open-wc/testing';
1+
import { expect, fixture } from '@open-wc/testing';
22
import { sendKeys } from '@web/test-runner-commands';
33
import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js';
44

@@ -176,10 +176,9 @@ abstract class TestCombobox extends ReactiveElement {
176176
});
177177

178178
it('collapses the listbox', async function() {
179-
expect(await a11ySnapshot())
180-
.to.not.axContainRole('listbox')
181-
.and
182-
.to.axContainQuery({ role: 'combobox', expanded: false });
179+
const snapshot = await a11ySnapshot();
180+
expect(snapshot).to.not.axContainRole('listbox');
181+
expect(snapshot).to.axContainQuery({ role: 'combobox', expanded: false });
183182
});
184183
});
185184
});
@@ -189,10 +188,9 @@ abstract class TestCombobox extends ReactiveElement {
189188
beforeEach(updateComplete);
190189

191190
it('collapses the listbox', async function() {
192-
expect(await a11ySnapshot())
193-
.to.not.axContainRole('listbox')
194-
.and
195-
.to.axContainQuery({ role: 'combobox', expanded: false });
191+
const snapshot = await a11ySnapshot();
192+
expect(snapshot).to.not.axContainRole('listbox');
193+
expect(snapshot).to.axContainQuery({ role: 'combobox', expanded: false });
196194
});
197195

198196
it('maintains DOM focus on the combobox', async function() {

elements/pf-v5-accordion/test/pf-accordion.spec.ts

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { expect, fixture, html, aTimeout, nextFrame } from '@open-wc/testing';
2-
import { sendKeys } from '@web/test-runner-commands';
32

4-
import { allUpdates, clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js';
3+
import { allUpdates, clickElementAtCenter, press as pressKey } from '@patternfly/pfe-tools/test/utils.js';
54
import { a11ySnapshot, querySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js';
65

76
// Import the element we're testing.
@@ -56,9 +55,9 @@ describe('<pf-v5-accordion>', function() {
5655
await allUpdates(element);
5756
}
5857

59-
function press(press: string) {
58+
function press(key: string) {
6059
return async function() {
61-
await sendKeys({ press });
60+
await pressKey(key);
6261
await allUpdates(element);
6362
};
6463
}
@@ -391,14 +390,7 @@ describe('<pf-v5-accordion>', function() {
391390
describe('Tab', function() {
392391
beforeEach(press('Tab'));
393392
it('blurs out of the accordion', async function() {
394-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
395-
});
396-
});
397-
398-
describe('Shift+Tab', function() {
399-
beforeEach(press('Shift+Tab'));
400-
it('blurs out of the accordion', async function() {
401-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
393+
expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true });
402394
});
403395
});
404396

@@ -479,7 +471,7 @@ describe('<pf-v5-accordion>', function() {
479471
describe('Tab', function() {
480472
beforeEach(press('Tab'));
481473
it('moves focus to the body', async function() {
482-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
474+
expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true });
483475
});
484476
});
485477

@@ -554,13 +546,6 @@ describe('<pf-v5-accordion>', function() {
554546
});
555547
});
556548

557-
describe('Shift+Tab', function() {
558-
beforeEach(press('Shift+Tab'));
559-
it('moves focus to the body', async function() {
560-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
561-
});
562-
});
563-
564549
describe('ArrowDown', function() {
565550
beforeEach(press('ArrowDown'));
566551
it('moves focus to the first header', async function() {
@@ -628,14 +613,8 @@ describe('<pf-v5-accordion>', function() {
628613

629614
describe('Tab', function() {
630615
beforeEach(press('Tab'));
631-
it('moves focus to the body', async function() {
632-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
633-
});
634-
describe('Shift+Tab', function() {
635-
beforeEach(press('Shift+Tab'));
636-
it('keeps focus on the link in the first panel', async function() {
637-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(panel1.querySelector('a'));
638-
});
616+
it('moves focus out of the accordion', async function() {
617+
expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true });
639618
});
640619
});
641620

@@ -732,7 +711,9 @@ describe('<pf-v5-accordion>', function() {
732711
describe('Home', function() {
733712
beforeEach(press('Home'));
734713
it('moves focus to the first header', async function() {
735-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(header1);
714+
expect(await a11ySnapshot())
715+
.axTreeFocusedNode.to.have
716+
.axName(header1.textContent!.trim());
736717
});
737718

738719
it('does not open other panels', function() {
@@ -822,7 +803,7 @@ describe('<pf-v5-accordion>', function() {
822803
describe('Shift+Tab', function() {
823804
beforeEach(press('Shift+Tab'));
824805
it('moves focus to the body', async function() {
825-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
806+
expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true });
826807
});
827808
});
828809

@@ -1111,25 +1092,23 @@ describe('<pf-v5-accordion>', function() {
11111092
});
11121093
beforeEach(() => allUpdates(element));
11131094
it('expands the first top-level pair', async function() {
1114-
const snapshot = await a11ySnapshot();
1115-
const expanded = snapshot?.children?.find(x => x.expanded);
1116-
expect(expanded?.name).to.equal(topLevelHeader1.textContent?.trim());
1095+
expect(await a11ySnapshot())
1096+
.to.axContainQuery({ name: topLevelHeader1.textContent?.trim(), expanded: true });
11171097
expect(topLevelHeader1.expanded).to.be.true;
11181098
expect(topLevelPanel1.hasAttribute('expanded')).to.be.true;
11191099
expect(topLevelPanel1.expanded).to.be.true;
11201100
});
11211101
it('collapses the second top-level pair', async function() {
1122-
const snapshot = await a11ySnapshot();
1123-
const header2 = querySnapshot(snapshot, { name: 'top-header-2' });
1124-
expect(header2).to.have.property('expanded', true);
1102+
expect(await a11ySnapshot())
1103+
.to.axContainQuery({ name: 'top-header-2', expanded: true });
11251104
});
11261105
it('collapses the first nested pair', async function() {
1127-
const snapshot = await a11ySnapshot();
1128-
expect(querySnapshot(snapshot, { name: 'nest-1-header-1' })).to.not.have.property('expanded');
1106+
expect(await a11ySnapshot())
1107+
.to.not.axContainQuery({ name: 'nest-1-header-1', expanded: true });
11291108
});
11301109
it('collapses the second nested pair', async function() {
1131-
const snapshot = await a11ySnapshot();
1132-
expect(querySnapshot(snapshot, { name: 'nest-2-header-1' })).to.not.have.property('expanded');
1110+
expect(await a11ySnapshot())
1111+
.to.not.axContainQuery({ name: 'nest-2-header-1', expanded: true });
11331112
});
11341113
});
11351114
});
@@ -1249,7 +1228,7 @@ describe('<pf-v5-accordion>', function() {
12491228
beforeEach(press('Tab'));
12501229
beforeEach(nextFrame);
12511230
it('should move focus back to the body', async function() {
1252-
expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body);
1231+
expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true });
12531232
});
12541233
});
12551234
});

elements/pf-v5-alert/pf-v5-alert.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { LitElement, html, type TemplateResult } from 'lit';
22
import { customElement } from 'lit/decorators/custom-element.js';
33
import { property } from 'lit/decorators/property.js';
4-
import { classMap } from 'lit/directives/class-map.js';
54
import { ifDefined } from 'lit/directives/if-defined.js';
65

76
import { observes } from '@patternfly/pfe-core/decorators.js';

0 commit comments

Comments
 (0)