diff --git a/.changeset/big-dingos-live.md b/.changeset/big-dingos-live.md new file mode 100644 index 0000000000..5f86e81069 --- /dev/null +++ b/.changeset/big-dingos-live.md @@ -0,0 +1,9 @@ +--- +"@patternfly/pfe-tools": major +--- + +**Test Runner**: migrate config from playwright-backed to puppeteer. + +Transitive dependencies have changed, so if your test files relied on playwright imports, +you'll need to update them. + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7868d925cd..76ae344537 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -98,7 +98,7 @@ jobs: name: SSR Tests (Playwright) runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.48.2-noble + image: mcr.microsoft.com/playwright:v1.57.0-noble steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 diff --git a/core/pfe-core/controllers/internals-controller.ts b/core/pfe-core/controllers/internals-controller.ts index 153b175a48..45cc6c4376 100644 --- a/core/pfe-core/controllers/internals-controller.ts +++ b/core/pfe-core/controllers/internals-controller.ts @@ -340,7 +340,7 @@ export class InternalsController implements ReactiveController, ARIAMixin { /** @see https://w3c.github.io/aria/#ref-for-dom-ariamixin-ariaactivedescendantelement-1 */ declare global { // https://github.com/webcomponents-cg/community-protocols/pull/75 - var _elementInternals: WeakMap; // eslint-disable-line no-var + var _elementInternals: WeakMap; interface ARIAMixin { ariaActiveDescendantElement: Element | null; ariaControlsElements: readonly Element[] | null; diff --git a/core/pfe-core/controllers/slot-controller-server.ts b/core/pfe-core/controllers/slot-controller-server.ts index 950a732109..cc01fb85c3 100644 --- a/core/pfe-core/controllers/slot-controller-server.ts +++ b/core/pfe-core/controllers/slot-controller-server.ts @@ -12,7 +12,7 @@ export class SlotController implements SlotControllerPublicAPI { static anonymousAttribute = 'ssr-hint-has-slotted-default' as const; - constructor(public host: ReactiveElement, ..._: SlotControllerArgs) { + constructor(public host: ReactiveElement, ..._args: SlotControllerArgs) { host.addController(this); } @@ -24,7 +24,7 @@ export class SlotController implements SlotControllerPublicAPI { .map(x => x.trim()); } - getSlotted(..._: (string | null)[]): T[] { + getSlotted(..._names: (string | null)[]): T[] { return []; } diff --git a/core/pfe-core/controllers/test/combobox-controller.spec.ts b/core/pfe-core/controllers/test/combobox-controller.spec.ts index 1249ab1d34..63e5fc0356 100644 --- a/core/pfe-core/controllers/test/combobox-controller.spec.ts +++ b/core/pfe-core/controllers/test/combobox-controller.spec.ts @@ -1,4 +1,4 @@ -import { expect, fixture, nextFrame } from '@open-wc/testing'; +import { expect, fixture } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; @@ -176,10 +176,9 @@ abstract class TestCombobox extends ReactiveElement { }); it('collapses the listbox', async function() { - expect(await a11ySnapshot()) - .to.not.axContainRole('listbox') - .and - .to.axContainQuery({ role: 'combobox', expanded: false }); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.not.axContainRole('listbox'); + expect(snapshot).to.axContainQuery({ role: 'combobox', expanded: false }); }); }); }); @@ -189,10 +188,9 @@ abstract class TestCombobox extends ReactiveElement { beforeEach(updateComplete); it('collapses the listbox', async function() { - expect(await a11ySnapshot()) - .to.not.axContainRole('listbox') - .and - .to.axContainQuery({ role: 'combobox', expanded: false }); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.not.axContainRole('listbox'); + expect(snapshot).to.axContainQuery({ role: 'combobox', expanded: false }); }); it('maintains DOM focus on the combobox', async function() { diff --git a/elements/pf-v5-accordion/test/pf-accordion.spec.ts b/elements/pf-v5-accordion/test/pf-accordion.spec.ts index c4a4526cef..a7390b1be9 100644 --- a/elements/pf-v5-accordion/test/pf-accordion.spec.ts +++ b/elements/pf-v5-accordion/test/pf-accordion.spec.ts @@ -1,7 +1,6 @@ import { expect, fixture, html, aTimeout, nextFrame } from '@open-wc/testing'; -import { sendKeys } from '@web/test-runner-commands'; -import { allUpdates, clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js'; +import { allUpdates, clickElementAtCenter, press as pressKey } from '@patternfly/pfe-tools/test/utils.js'; import { a11ySnapshot, querySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; // Import the element we're testing. @@ -56,9 +55,9 @@ describe('', function() { await allUpdates(element); } - function press(press: string) { + function press(key: string) { return async function() { - await sendKeys({ press }); + await pressKey(key); await allUpdates(element); }; } @@ -391,14 +390,7 @@ describe('', function() { describe('Tab', function() { beforeEach(press('Tab')); it('blurs out of the accordion', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); - }); - }); - - describe('Shift+Tab', function() { - beforeEach(press('Shift+Tab')); - it('blurs out of the accordion', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); + expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true }); }); }); @@ -479,7 +471,7 @@ describe('', function() { describe('Tab', function() { beforeEach(press('Tab')); it('moves focus to the body', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); + expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true }); }); }); @@ -554,13 +546,6 @@ describe('', function() { }); }); - describe('Shift+Tab', function() { - beforeEach(press('Shift+Tab')); - it('moves focus to the body', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); - }); - }); - describe('ArrowDown', function() { beforeEach(press('ArrowDown')); it('moves focus to the first header', async function() { @@ -628,14 +613,8 @@ describe('', function() { describe('Tab', function() { beforeEach(press('Tab')); - it('moves focus to the body', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); - }); - describe('Shift+Tab', function() { - beforeEach(press('Shift+Tab')); - it('keeps focus on the link in the first panel', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(panel1.querySelector('a')); - }); + it('moves focus out of the accordion', async function() { + expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true }); }); }); @@ -732,7 +711,9 @@ describe('', function() { describe('Home', function() { beforeEach(press('Home')); it('moves focus to the first header', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(header1); + expect(await a11ySnapshot()) + .axTreeFocusedNode.to.have + .axName(header1.textContent!.trim()); }); it('does not open other panels', function() { @@ -822,7 +803,7 @@ describe('', function() { describe('Shift+Tab', function() { beforeEach(press('Shift+Tab')); it('moves focus to the body', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); + expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true }); }); }); @@ -1111,25 +1092,23 @@ describe('', function() { }); beforeEach(() => allUpdates(element)); it('expands the first top-level pair', async function() { - const snapshot = await a11ySnapshot(); - const expanded = snapshot?.children?.find(x => x.expanded); - expect(expanded?.name).to.equal(topLevelHeader1.textContent?.trim()); + expect(await a11ySnapshot()) + .to.axContainQuery({ name: topLevelHeader1.textContent?.trim(), expanded: true }); expect(topLevelHeader1.expanded).to.be.true; expect(topLevelPanel1.hasAttribute('expanded')).to.be.true; expect(topLevelPanel1.expanded).to.be.true; }); it('collapses the second top-level pair', async function() { - const snapshot = await a11ySnapshot(); - const header2 = querySnapshot(snapshot, { name: 'top-header-2' }); - expect(header2).to.have.property('expanded', true); + expect(await a11ySnapshot()) + .to.axContainQuery({ name: 'top-header-2', expanded: true }); }); it('collapses the first nested pair', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshot(snapshot, { name: 'nest-1-header-1' })).to.not.have.property('expanded'); + expect(await a11ySnapshot()) + .to.not.axContainQuery({ name: 'nest-1-header-1', expanded: true }); }); it('collapses the second nested pair', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshot(snapshot, { name: 'nest-2-header-1' })).to.not.have.property('expanded'); + expect(await a11ySnapshot()) + .to.not.axContainQuery({ name: 'nest-2-header-1', expanded: true }); }); }); }); @@ -1249,7 +1228,7 @@ describe('', function() { beforeEach(press('Tab')); beforeEach(nextFrame); it('should move focus back to the body', async function() { - expect(await a11ySnapshot()).to.have.axTreeFocusOn(document.body); + expect(await a11ySnapshot()).to.not.axContainQuery({ role: 'button', focused: true }); }); }); }); diff --git a/elements/pf-v5-alert/pf-v5-alert.ts b/elements/pf-v5-alert/pf-v5-alert.ts index 76f94acc65..725bb42592 100644 --- a/elements/pf-v5-alert/pf-v5-alert.ts +++ b/elements/pf-v5-alert/pf-v5-alert.ts @@ -1,7 +1,6 @@ import { LitElement, html, type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { observes } from '@patternfly/pfe-core/decorators.js'; diff --git a/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts b/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts index f54012c528..2524318a49 100644 --- a/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts +++ b/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts @@ -5,10 +5,7 @@ import { setViewport, sendKeys } from '@web/test-runner-commands'; import { allUpdates } from '@patternfly/pfe-tools/test/utils.js'; import { PfV5BackToTop } from '../pf-v5-back-to-top.js'; -import { type A11yTreeSnapshot, a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; - -const takeProps = (props: string[]) => (obj: object) => - Object.fromEntries(Object.entries(obj).filter(([k]) => props.includes(k))); +import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; describe('', function() { it('imperatively instantiates', function() { @@ -33,7 +30,6 @@ describe('', function() { describe('when rendered in a viewport with a height smaller then content length', function() { let element: PfV5BackToTop; - let snapshot: A11yTreeSnapshot; beforeEach(async function() { await setViewport({ width: 320, height: 640 }); @@ -46,18 +42,17 @@ describe('', function() { `); element = container.querySelector('pf-v5-back-to-top')!; - snapshot = await a11ySnapshot(); - await allUpdates(element); }); - it('should be hidden on init', function() { - const { children } = snapshot; - expect(children).to.be.undefined; + it('should be hidden on init', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.not.axContainRole('link'); }); - it('should not be accessible', function() { - expect(snapshot.children).to.be.undefined; + it('should not be accessible', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.not.axContainName('Back to top'); }); describe('when scrolled 401px', function() { @@ -65,11 +60,11 @@ describe('', function() { window.scrollTo({ top: 401, behavior: 'instant' }); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should be visible', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]); + it('should be visible', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); it('should be accessible', async function() { @@ -95,11 +90,11 @@ describe('', function() { await nextFrame(); element.alwaysVisible = true; await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should be visible', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]); + it('should be visible', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); it('should be accessible', async function() { @@ -122,12 +117,10 @@ describe('', function() { beforeEach(async function() { element.scrollDistance = 1000; await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should be hidden', function() { - const { children } = snapshot; - expect(children).to.be.undefined; + it('should be hidden', async function() { + expect(await a11ySnapshot()).to.not.axContainRole('link'); }); describe('when scrolled 1001px', function() { @@ -135,11 +128,11 @@ describe('', function() { window.scrollTo({ top: 1001, behavior: 'instant' }); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should be visible', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]); + it('should be visible', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); }); }); @@ -147,7 +140,6 @@ describe('', function() { describe('when rendered in an element with an overflowed height', function() { let element: PfV5BackToTop; - let snapshot: A11yTreeSnapshot; beforeEach(async function() { window.scrollTo({ top: 0, behavior: 'instant' }); @@ -160,13 +152,11 @@ describe('', function() { `); element = container.querySelector('pf-v5-back-to-top')!; await allUpdates(element); - - snapshot = await a11ySnapshot({ selector: 'pf-v5-back-to-top' }); }); - it('should be hidden on init', function() { - const { children } = snapshot; - expect(children).to.be.undefined; + it('should be hidden on init', async function() { + const snapshot = await a11ySnapshot({ selector: 'pf-v5-back-to-top' }); + expect(snapshot?.children).to.not.be.ok; }); describe('when scrolled 401px', function() { @@ -176,18 +166,17 @@ describe('', function() { scrollableElement.dispatchEvent(new Event('scroll')); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should be visible', function() { - expect(snapshot.children?.at(0)?.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]); + it('should be visible', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); }); }); describe('when no text is provided', function() { let element: PfV5BackToTop; - let snapshot: A11yTreeSnapshot; describe('as a link', function() { beforeEach(async function() { @@ -209,11 +198,11 @@ describe('', function() { window.scrollTo({ top: 401, behavior: 'instant' }); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should have a label of "Back to top"', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]); + it('should have a label of "Back to top"', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); }); }); @@ -238,11 +227,11 @@ describe('', function() { window.scrollTo({ top: 401, behavior: 'instant' }); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should have a label of "Back to top"', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'button', name: 'Back to top' }]); + it('should have a label of "Back to top"', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'button', name: 'Back to top' }); }); }); }); @@ -250,7 +239,6 @@ describe('', function() { describe('when a label is provided', function() { let element: PfV5BackToTop; - let snapshot: A11yTreeSnapshot; describe('as a link', function() { beforeEach(async function() { @@ -272,11 +260,11 @@ describe('', function() { window.scrollTo({ top: 401, behavior: 'instant' }); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should have a label of "Return to top"', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Return to top' }]); + it('should have a label of "Return to top"', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'link', name: 'Return to top' }); }); }); }); @@ -301,11 +289,11 @@ describe('', function() { window.scrollTo({ top: 401, behavior: 'instant' }); await nextFrame(); await allUpdates(element); - snapshot = await a11ySnapshot(); }); - it('should have a label of "Return to top"', function() { - expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'button', name: 'Return to top' }]); + it('should have a label of "Return to top"', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'button', name: 'Return to top' }); }); }); }); diff --git a/elements/pf-v5-chip/test/pf-chip-group.spec.ts b/elements/pf-v5-chip/test/pf-chip-group.spec.ts index 2952ac858e..3ea0a9e3f7 100644 --- a/elements/pf-v5-chip/test/pf-chip-group.spec.ts +++ b/elements/pf-v5-chip/test/pf-chip-group.spec.ts @@ -71,7 +71,7 @@ describe('', async function() { beforeEach(updateComplete); it('should show all chips', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4); + expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(4); }); it('should show collapse button', async function() { const snapshot = await a11ySnapshot(); @@ -104,11 +104,8 @@ describe('', async function() { beforeEach(updateComplete); it('should have close button', async function() { - const snapshot = await a11ySnapshot(); - const last = snapshot.children?.at(-1); - expect(last?.name).to.equal('Close'); - expect(last?.role).to.equal('button'); - expect(last?.description).to.not.be.ok; + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'button', name: 'Close' }); }); describe('clicking close button', function() { @@ -118,7 +115,7 @@ describe('', async function() { beforeEach(updateComplete); it('should remove element', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children).to.not.be.ok; + expect(snapshot).to.not.axContainRole('button'); }); }); }); @@ -146,8 +143,7 @@ describe('', async function() { }); it('has accessible label', function() { - const [offscreen] = snapshot.children!; - expect(offscreen?.name).to.equal('My Chip Group'); + expect(snapshot).to.axContainName('My Chip Group'); }); it('is accessible', async function() { @@ -185,7 +181,7 @@ describe('', async function() { }); it('only 2 chips should be visible', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(2); + expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(2); }); }); @@ -206,7 +202,7 @@ describe('', async function() { it('all 4 chips should be visible', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4); + expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(4); }); describe('keyboard navigating with arrow keys to third chip and pressing enter', function() { @@ -218,7 +214,7 @@ describe('', async function() { it('should remove third chip', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.name === 'Chip 3')).to.not.be.ok; + expect(snapshot).to.not.axContainName('Chip 3'); }); it('should focus on close button', async function() { diff --git a/elements/pf-v5-chip/test/pf-chip.spec.ts b/elements/pf-v5-chip/test/pf-chip.spec.ts index ca2a293cc2..23487f6be0 100644 --- a/elements/pf-v5-chip/test/pf-chip.spec.ts +++ b/elements/pf-v5-chip/test/pf-chip.spec.ts @@ -3,7 +3,7 @@ import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import { PfV5Chip } from '../pf-v5-chip.js'; import { sendKeys } from '@web/test-runner-commands'; -import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { a11ySnapshot, querySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js'; @@ -47,8 +47,8 @@ describe('', async function() { beforeEach(() => element.focus()); it('should focus', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.at(0)?.name).to.equal(element.accessibleCloseLabel); - expect(snapshot.children?.at(0)?.focused).to.be.true; + const focused = querySnapshot(snapshot, { focused: true }); + expect(focused).to.have.property('name', element.accessibleCloseLabel); }); }); @@ -56,8 +56,8 @@ describe('', async function() { beforeEach(press('Tab')); it('should focus', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.at(0)?.name).to.equal(element.accessibleCloseLabel); - expect(snapshot.children?.at(0)?.focused).to.be.true; + const focused = querySnapshot(snapshot, { focused: true }); + expect(focused).to.have.property('name', element.accessibleCloseLabel); }); describe('pressing Enter', async function() { @@ -85,7 +85,7 @@ describe('', async function() { it('should not have a close button', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.name === 'Close')).to.not.be.ok; + expect(snapshot).to.not.axContainName('Close'); }); describe('calling focus', function() { @@ -133,7 +133,7 @@ describe('', async function() { it('should not have a button', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children).to.be.undefined; + expect(snapshot).to.not.axContainRole('button'); }); }); }); diff --git a/elements/pf-v5-dropdown/test/pf-dropdown.spec.ts b/elements/pf-v5-dropdown/test/pf-dropdown.spec.ts index 9d7c9ffbdf..31fc19ef16 100644 --- a/elements/pf-v5-dropdown/test/pf-dropdown.spec.ts +++ b/elements/pf-v5-dropdown/test/pf-dropdown.spec.ts @@ -53,9 +53,7 @@ describe('', function() { }); it('should hide dropdown content from assistive technology', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot.children?.find(x => x.role === 'menu'); - expect(menu).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('menu'); }); describe('pressing Enter', function() { @@ -64,17 +62,14 @@ describe('', function() { beforeEach(updateComplete); it('should show menu', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot?.children?.find(x => x.role === 'menu'); - expect(menu).to.be.ok; - expect(menu?.children?.length).to.equal(2); + expect(await a11ySnapshot()).to.axContainRole('menu'); }); it('should focus on first menu item', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot?.children?.find(x => x.role === 'menu'); - const focused = menu?.children?.find(x => x.focused); - expect(focused).to.deep.equal({ role: 'menuitem', name: 'item 1', focused: true }); + expect(await a11ySnapshot()) + .axTreeFocusedNode.to.have + .axRole('menuitem') + .and.axName('item 1'); }); describe('pressing ArrowDown', function() { @@ -83,11 +78,11 @@ describe('', function() { await element.updateComplete; }); - it('should focus on secondc menu item', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot?.children?.find(x => x.role === 'menu'); - const focused = menu?.children?.find(x => x.focused); - expect(focused).to.deep.equal({ role: 'menuitem', name: 'item 2', focused: true }); + it('should focus on second menu item', async function() { + expect(await a11ySnapshot()) + .axTreeFocusedNode.to.have + .axRole('menuitem') + .and.axName('item 2'); }); describe('pressing Escape', function() { @@ -96,10 +91,7 @@ describe('', function() { }); it('should close menu', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot?.children?.find(x => x.role === 'menu'); - expect(snapshot.children?.length).to.equal(1); - expect(menu).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('menu'); }); }); }); @@ -112,9 +104,8 @@ describe('', function() { }); it('should disable toggle button', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.length).to.equal(1); - expect(snapshot.children?.at(0)?.disabled).to.be.true; + expect(await a11ySnapshot()) + .to.axContainQuery({ disabled: true }); }); describe('pressing Enter', function() { @@ -124,10 +115,7 @@ describe('', function() { }); it('should show menu', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot?.children?.find(x => x.role === 'menu'); - expect(menu).to.be.ok; - expect(menu?.children?.length).to.equal(2); + expect(await a11ySnapshot()).to.axContainRole('menu'); }); }); @@ -138,10 +126,7 @@ describe('', function() { }); it('should show menu', async function() { - const snapshot = await a11ySnapshot(); - const menu = snapshot?.children?.find(x => x.role === 'menu'); - expect(menu).to.be.ok; - expect(menu?.children?.length).to.equal(2); + expect(await a11ySnapshot()).to.axContainRole('menu'); }); }); }); diff --git a/elements/pf-v5-hint/test/pf-hint.spec.ts b/elements/pf-v5-hint/test/pf-hint.spec.ts index 70673798b3..b0e2af469c 100644 --- a/elements/pf-v5-hint/test/pf-hint.spec.ts +++ b/elements/pf-v5-hint/test/pf-hint.spec.ts @@ -22,16 +22,16 @@ describe('', function() { }); describe('basic hint', function() { - let element: PfV5Hint; beforeEach(async function() { - element = await createFixture(html` + await createFixture(html` Welcome to the new documentation experience. `); }); it('should render body content, and not title footer, or actions', async function() { - const snap = await a11ySnapshot(); - expect(snap.children?.length).to.equal(1); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainName('Welcome to the new documentation experience.'); + expect(snapshot).to.not.axContainRole('button'); }); }); @@ -48,8 +48,9 @@ describe('', function() { }); it('should render title and body content', async function() { - const snap = await a11ySnapshot(); - expect(snap.children?.length).to.equal(2); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainName('Do more with Find it Fix it capabilities'); + expect(snapshot).to.axContainName('Upgrade to Red Hat Smart Management.'); }); }); @@ -86,11 +87,10 @@ describe('', function() { }); it('should render title, body, and actions', async function() { - const { children: [actions, title, body, ...rest] = [] } = await a11ySnapshot(); - expect(actions.role).to.equal('button'); - expect(title.role).to.equal('text'); - expect(body.role).to.equal('text'); - expect(rest.length).to.equal(0); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainQuery({ role: 'button' }); + expect(snapshot).to.axContainName('Do more with Find it Fix it capabilities'); + expect(snapshot).to.axContainName('Upgrade to Red Hat Smart Management.'); }); }); }); diff --git a/elements/pf-v5-label-group/test/pf-label-group.spec.ts b/elements/pf-v5-label-group/test/pf-label-group.spec.ts index a2578da959..a55b051fa2 100644 --- a/elements/pf-v5-label-group/test/pf-label-group.spec.ts +++ b/elements/pf-v5-label-group/test/pf-label-group.spec.ts @@ -53,7 +53,7 @@ describe('', function() { }); it('should show all labels', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(4); + expect(querySnapshotAll(snapshot, { name: /^Label/ })).to.have.length(4); }); it('should show collapse text', async function() { const snapshot = await a11ySnapshot(); @@ -80,9 +80,7 @@ describe('', function() { it('should have a close button', async function() { const snapshot = await a11ySnapshot(); - const last = snapshot.children?.at(-1); - expect(last?.name).to.equal('Close'); - expect(last?.role).to.equal('button'); + expect(snapshot).to.axContainQuery({ role: 'button', name: 'Close' }); }); describe('clicking close button', function() { @@ -132,7 +130,7 @@ describe('', function() { it('only 2 labels should be visible', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(2); + expect(querySnapshotAll(snapshot, { name: /^Label/ })).to.have.length(2); }); }); @@ -150,7 +148,7 @@ describe('', function() { it('all 4 labels should be visible', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(4); + expect(querySnapshotAll(snapshot, { name: /^Label/ })).to.have.length(4); }); }); @@ -167,7 +165,7 @@ describe('', function() { it('should display the category text', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.some(x => x.name === 'Group')).to.be.true; + expect(snapshot).to.axContainName('Group'); }); }); }); diff --git a/elements/pf-v5-popover/test/pf-popover.spec.ts b/elements/pf-v5-popover/test/pf-popover.spec.ts index 3c8ac3daba..59c1b67de4 100644 --- a/elements/pf-v5-popover/test/pf-popover.spec.ts +++ b/elements/pf-v5-popover/test/pf-popover.spec.ts @@ -53,8 +53,7 @@ describe('', function() { .to.be.an.instanceof(PfV5Popover); }); it('should not report anything to assistive technology', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('button'); }); }); @@ -81,8 +80,7 @@ describe('', function() { it('should be accessible', expectA11yAxe); it('should hide popover content from assistive technology', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'dialog')).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('dialog'); }); describe('tabbing to the trigger', function() { @@ -110,18 +108,14 @@ describe('', function() { beforeEach(press('Enter')); beforeEach(updateComplete); it('should show popover content to assistive technology', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'dialog')).to.be.ok; + expect(await a11ySnapshot()).to.axContainRole('dialog'); }); describe('then pressing Enter again', function() { beforeEach(updateComplete); beforeEach(press('Enter')); beforeEach(updateComplete); it('should hide popover content from assistive technology', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot?.children?.length).to.equal(1); - const dialog = snapshot.children?.find(x => x.role === 'dialog'); - expect(dialog).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('dialog'); }); }); describe('then pressing Escape', function() { @@ -129,10 +123,7 @@ describe('', function() { beforeEach(press('Escape')); beforeEach(updateComplete); it('should hide popover content from assistive technology', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot?.children?.length).to.equal(1); - const dialog = snapshot.children?.find(x => x.role === 'dialog'); - expect(dialog).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('dialog'); }); }); }); @@ -171,8 +162,7 @@ describe('', function() { }); it('starts closed', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'dialog')).to.not.be.ok; + expect(await a11ySnapshot()).to.not.axContainRole('dialog'); }); describe('clicking the trigger', function() { @@ -180,8 +170,7 @@ describe('', function() { beforeEach(clickButton1); beforeEach(updateComplete); it('shows the popover', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'dialog')).to.be.ok; + expect(await a11ySnapshot()).to.axContainRole('dialog'); }); }); @@ -197,14 +186,9 @@ describe('', function() { beforeEach(updateComplete); it('remains closed', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot).to.deep.equal({ - name: '', - role: 'WebArea', - children: [ - { role: 'button', name: 'Toggle popover 1', focused: true }, - { role: 'button', name: 'Toggle popover 2' }, - ], - }); + expect(snapshot).to.not.axContainRole('dialog'); + expect(snapshot).to.axContainQuery({ role: 'button', name: 'Toggle popover 1', focused: true }); + expect(snapshot).to.axContainQuery({ role: 'button', name: 'Toggle popover 2' }); }); }); describe('clicking the sibling button', function() { @@ -212,32 +196,19 @@ describe('', function() { beforeEach(clickButton2); beforeEach(updateComplete); it('shows the popover', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'dialog')).to.be.ok; + expect(await a11ySnapshot()).to.axContainRole('dialog'); }); }); }); describe('then pressing the Enter key', function() { beforeEach(updateComplete); - // Close the popover beforeEach(press('Enter')); beforeEach(updateComplete); it('closes the popover', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot).to.deep.equal({ - role: 'WebArea', - name: '', - children: [ - { - name: 'Toggle popover 1', - role: 'button', - }, - { - name: 'Toggle popover 2', - role: 'button', - }, - ], - }); + expect(snapshot).to.not.axContainRole('dialog'); + expect(snapshot).to.axContainQuery({ role: 'button', name: 'Toggle popover 1' }); + expect(snapshot).to.axContainQuery({ role: 'button', name: 'Toggle popover 2' }); }); }); }); diff --git a/elements/pf-v5-search-input/demo/index.html b/elements/pf-v5-search-input/demo/index.html index 94abec8be0..e993825c8b 100644 --- a/elements/pf-v5-search-input/demo/index.html +++ b/elements/pf-v5-search-input/demo/index.html @@ -29,9 +29,7 @@ const searchInput = document.getElementById('search-input'); searchInput.addEventListener('change', (event) => { - /* eslint-disable no-console */ console.log('Selected:', event.target.value); - /* eslint-disable no-console */ }); diff --git a/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html b/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html index 563212ff17..566cbe206e 100644 --- a/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html +++ b/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html @@ -33,17 +33,13 @@ const searchInput = document.getElementById('search-input'); searchInput.addEventListener('change', (event) => { - /* eslint-disable no-console */ console.log('Selected:', event.target.value); - /* eslint-disable no-console */ }); const form = document.querySelector('form.container'); form.addEventListener('submit', (event) =>{ event.preventDefault(); - /* eslint-disable no-console */ console.log("Value:", form.elements.search?.value); - /* eslint-disable no-console */ }) diff --git a/elements/pf-v5-search-input/test/pf-search-input.spec.ts b/elements/pf-v5-search-input/test/pf-search-input.spec.ts index 6317a2e001..0d04c97baa 100644 --- a/elements/pf-v5-search-input/test/pf-search-input.spec.ts +++ b/elements/pf-v5-search-input/test/pf-search-input.spec.ts @@ -1,7 +1,7 @@ import { aTimeout, expect, html, nextFrame } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import { PfV5SearchInput } from '../pf-v5-search-input.js'; -import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { a11ySnapshot, querySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { sendKeys } from '@web/test-runner-commands'; import { clickElementAtCenter, clickElementAtOffset } from '@patternfly/pfe-tools/test/utils.js'; @@ -281,8 +281,8 @@ describe('', function() { describe('Tab', function() { beforeEach(press('Tab')); - it('does not focus the combobox button', async function() { - expect(await a11ySnapshot()).to.not.have.axTreeFocusedNode; + it('collapses the listbox', async function() { + expect(await a11ySnapshot()).to.not.axContainRole('listbox'); }); }); }); @@ -772,7 +772,7 @@ describe('', function() { beforeEach(updateComplete); it('does not error', async function() { const snapshot = await a11ySnapshot(); - const [, , listbox] = snapshot.children ?? []; + const listbox = querySnapshot(snapshot, { role: 'listbox' }); expect(listbox?.children).to.not.be.ok; }); }); diff --git a/elements/pf-v5-select/test/pf-select.spec.ts b/elements/pf-v5-select/test/pf-select.spec.ts index ed33aff65a..8725016f50 100644 --- a/elements/pf-v5-select/test/pf-select.spec.ts +++ b/elements/pf-v5-select/test/pf-select.spec.ts @@ -2,7 +2,7 @@ import { expect, html, aTimeout, nextFrame } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import { PfV5Select } from '../pf-v5-select.js'; import { sendKeys } from '@web/test-runner-commands'; -import { a11ySnapshot, querySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { a11ySnapshot, querySnapshot, querySnapshotAll } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { clickElementAtCenter, clickElementAtOffset } from '@patternfly/pfe-tools/test/utils.js'; import type { PfV5Option } from '../pf-v5-option.js'; @@ -766,15 +766,15 @@ describe('', function() { it('expands the listbox', async function() { expect(element.expanded).to.be.true; - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.at(1)).to.be.ok; - expect(snapshot.children?.at(1)?.role).to.equal('listbox'); + expect(await a11ySnapshot()).to.axContainRole('listbox'); }); it('should NOT use checkbox role for options', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.at(1)?.children?.filter(x => x.role === 'checkbox')?.length) - .to.equal(0); + const listbox = querySnapshot(snapshot, { role: 'listbox' }); + expect(listbox).to.be.ok; + const checkboxes = querySnapshotAll(listbox!, { role: 'checkbox' }); + expect(checkboxes.length).to.equal(0); }); }); @@ -828,9 +828,7 @@ describe('', function() { expect(element.expanded).to.be.false; }); it('hides the listbox', async function() { - const snapshot = await a11ySnapshot(); - const listbox = snapshot.children?.find(x => x.role === 'listbox'); - expect(listbox).to.be.undefined; + expect(await a11ySnapshot()).to.not.axContainRole('listbox'); }); }); @@ -841,13 +839,12 @@ describe('', function() { expect(element.expanded).to.be.false; }); it('hides the listbox', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.at(1)).to.be.undefined; + expect(await a11ySnapshot()).to.not.axContainRole('listbox'); }); it('focuses the button', async function() { - const snapshot = await a11ySnapshot(); - const focused = querySnapshot(snapshot, { focused: true }); - expect(focused?.role).to.equal('combobox'); + expect(await a11ySnapshot()) + .to.have.axTreeFocusedNode + .and.to.have.axRole('combobox'); }); }); @@ -1485,7 +1482,7 @@ describe('', function() { beforeEach(updateComplete); it('does not error', async function() { const snapshot = await a11ySnapshot(); - const [, , listbox] = snapshot.children ?? []; + const listbox = querySnapshot(snapshot, { role: 'listbox' }); expect(listbox?.children).to.not.be.ok; }); }); diff --git a/elements/pf-v5-tabs/test/pf-tabs.spec.ts b/elements/pf-v5-tabs/test/pf-tabs.spec.ts index d2457f2d59..df8e2ee391 100644 --- a/elements/pf-v5-tabs/test/pf-tabs.spec.ts +++ b/elements/pf-v5-tabs/test/pf-tabs.spec.ts @@ -1,6 +1,6 @@ import { expect, html, nextFrame } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { a11ySnapshot, querySnapshot, querySnapshotAll } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { setViewport, sendKeys } from '@web/test-runner-commands'; import { allUpdates } from '@patternfly/pfe-tools/test/utils.js'; @@ -54,7 +54,7 @@ describe('', function() { it('should show the first tab as selected in the accessibility tree', async function() { const snapshot = await a11ySnapshot(); - const tabs = snapshot.children?.filter(x => x.role === 'tab') ?? []; + const tabs = querySnapshotAll(snapshot, { role: 'tab' }); const [first, ...rest] = tabs; expect(first).to.have.property('selected', true); for (const tab of rest) { @@ -134,7 +134,7 @@ describe('', function() { it('should show the second tab as selected in the accessibility tree', async function() { const snapshot = await a11ySnapshot(); - const tabs = snapshot.children?.filter(x => x.role === 'tab') ?? []; + const tabs = querySnapshotAll(snapshot, { role: 'tab' }); const [first, second, third] = tabs; expect(first).to.not.have.property('selected', true); expect(second).to.have.property('selected', true); @@ -157,7 +157,9 @@ describe('', function() { it('should activate the third panel', async function() { const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'tabpanel')?.name).to.equal('tab-3'); + const panel = querySnapshot(snapshot, { role: 'tabpanel' }); + expect(panel).to.not.be.null; + expect(panel!).to.have.property('name', 'tab-3'); }); describe('then setting the first tab\'s `disabled` attribute', function() { @@ -169,8 +171,7 @@ describe('', function() { it('should disable the button', async function() { const snapshot = await a11ySnapshot(); - const disabledTab = snapshot.children?.find(x => x.role === 'tab' && x.disabled); - expect(disabledTab).to.be.ok; + expect(snapshot).to.axContainQuery({ role: 'tab', disabled: true }); }); describe('and clicking the disabled tab', function() { @@ -186,8 +187,7 @@ describe('', function() { }); it('should present the third panel to the ax tree', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'tabpanel')?.name).to.equal('tab-3'); + expect(await a11ySnapshot()).to.axContainQuery({ role: 'tabpanel', name: 'tab-3' }); }); }); @@ -203,8 +203,7 @@ describe('', function() { }); it('should present the third panel to the ax tree', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.role === 'tabpanel')?.name).to.equal('tab-3'); + expect(await a11ySnapshot()).to.axContainQuery({ role: 'tabpanel', name: 'tab-3' }); }); }); }); diff --git a/elements/pf-v5-tooltip/test/pf-tooltip.spec.ts b/elements/pf-v5-tooltip/test/pf-tooltip.spec.ts index 06317a94d9..48482ce25b 100644 --- a/elements/pf-v5-tooltip/test/pf-tooltip.spec.ts +++ b/elements/pf-v5-tooltip/test/pf-tooltip.spec.ts @@ -1,5 +1,4 @@ import { expect, html, fixture } from '@open-wc/testing'; -import type { A11yTreeSnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { PfV5Tooltip } from '../pf-v5-tooltip.js'; import { setViewport, sendMouse } from '@web/test-runner-commands'; @@ -7,7 +6,6 @@ import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; describe('', function() { let element: PfV5Tooltip; - let snapshot: A11yTreeSnapshot; beforeEach(async function() { await setViewport({ width: 1000, height: 1000 }); @@ -31,7 +29,6 @@ describe('', function() { element = await fixture(html` Tooltip `); - snapshot = await a11ySnapshot(); }); it('should be accessible', async function() { @@ -39,9 +36,9 @@ describe('', function() { }); it('should hide tooltip content from assistive technology', async function() { - expect(snapshot.children).to.deep.equal([ - { name: 'Tooltip', role: 'text' }, - ]); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainName('Tooltip'); + expect(snapshot).to.not.axContainName('Content'); }); describe('hovering the element', function() { @@ -49,13 +46,11 @@ describe('', function() { const { x, y } = element.getBoundingClientRect(); await sendMouse({ position: [x + 5, y + 5], type: 'move' }); await element.updateComplete; - snapshot = await a11ySnapshot(); }); it('should show tooltip content to assistive technology', async function() { - expect(snapshot.children).to.deep.equal([ - { name: 'Tooltip', role: 'text' }, - { name: 'Content', role: 'text' }, - ]); + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainName('Tooltip'); + expect(snapshot).to.axContainName('Content'); }); }); }); diff --git a/package-lock.json b/package-lock.json index 290aa8bfd4..1c4b655e2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2417,64 +2417,17 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", - "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "playwright": "1.48.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@playwright/test/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/@playwright/test/node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "license": "Apache-2.0", "peer": true, "dependencies": { - "playwright-core": "1.59.1" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/@playwright/test/node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "playwright-core": "cli.js" - }, "engines": { "node": ">=18" } @@ -4845,68 +4798,6 @@ "node": ">=18.0.0" } }, - "node_modules/@web/test-runner-playwright": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.1.tgz", - "integrity": "sha512-l9tmX0LtBqMaKAApS4WshpB87A/M8sOHZyfCobSGuYqnREgz5rqQpX314yx+4fwHXLLTa5N64mTrawsYkLjliw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@web/test-runner-core": "^0.13.0", - "@web/test-runner-coverage-v8": "^0.8.0", - "playwright": "^1.53.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/test-runner-playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/@web/test-runner-playwright/node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "playwright-core": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/@web/test-runner-playwright/node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@web/test-runner/node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -6353,7 +6244,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", - "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -7242,7 +7132,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -13243,6 +13132,28 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "24.42.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.42.0.tgz", + "integrity": "sha512-94MoPfFp2eY3eYIMdINkez4IOP5TMHntlZbVx06fHlQTtiQiYgaY0L2Zzfod8PVUkPqP7m3Qlre2v8YS8cudPA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@puppeteer/browsers": "2.13.0", + "chromium-bidi": "14.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1595872", + "puppeteer-core": "24.42.0", + "typed-query-selector": "^2.12.1" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/puppeteer-core": { "version": "24.42.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.42.0.tgz", @@ -16095,7 +16006,7 @@ "@koa/router": "^15.1.1", "@lit-labs/ssr": "^4.0.0", "@open-wc/testing": "^4.0.0", - "@playwright/test": "~1.48.0", + "@playwright/test": "~1.57.0", "@pwrs/mappa": "^0.0.4", "@rollup/plugin-replace": "^6.0.3", "@web/dev-server": "^0.4.6", @@ -16103,9 +16014,9 @@ "@web/dev-server-import-maps": "^0.2.1", "@web/dev-server-rollup": "^0.6.4", "@web/test-runner": "^0.20.2", + "@web/test-runner-chrome": "^0.18.0", "@web/test-runner-commands": "^0.9.0", "@web/test-runner-junit-reporter": "^0.8.0", - "@web/test-runner-playwright": "^0.11.1", "chalk": "^5.6.2", "clean-css": "^5.3.3", "colorjs.io": "^0.6.0", @@ -16127,6 +16038,7 @@ "nunjucks": "^3.2.4", "patch-package": "^8.0.1", "playwright": "~1.57.0", + "puppeteer": "^24.0.0", "rollup-plugin-lit-css": "^6.0.0", "sinon": "^21.0.1", "ts-lit-plugin": "^2.0.2", diff --git a/tools/pfe-tools/package.json b/tools/pfe-tools/package.json index a48a09aba4..9ad5615340 100644 --- a/tools/pfe-tools/package.json +++ b/tools/pfe-tools/package.json @@ -70,7 +70,7 @@ "@koa/router": "^15.1.1", "@lit-labs/ssr": "^4.0.0", "@open-wc/testing": "^4.0.0", - "@playwright/test": "~1.48.0", + "@playwright/test": "~1.57.0", "@pwrs/mappa": "^0.0.4", "@rollup/plugin-replace": "^6.0.3", "@web/dev-server": "^0.4.6", @@ -78,9 +78,9 @@ "@web/dev-server-import-maps": "^0.2.1", "@web/dev-server-rollup": "^0.6.4", "@web/test-runner": "^0.20.2", + "@web/test-runner-chrome": "^0.18.0", "@web/test-runner-commands": "^0.9.0", "@web/test-runner-junit-reporter": "^0.8.0", - "@web/test-runner-playwright": "^0.11.1", "chalk": "^5.6.2", "clean-css": "^5.3.3", "colorjs.io": "^0.6.0", @@ -102,6 +102,7 @@ "nunjucks": "^3.2.4", "patch-package": "^8.0.1", "playwright": "~1.57.0", + "puppeteer": "^24.0.0", "rollup-plugin-lit-css": "^6.0.0", "sinon": "^21.0.1", "ts-lit-plugin": "^2.0.2", diff --git a/tools/pfe-tools/test/config.ts b/tools/pfe-tools/test/config.ts index fa128e7c90..cfce355d59 100644 --- a/tools/pfe-tools/test/config.ts +++ b/tools/pfe-tools/test/config.ts @@ -1,7 +1,8 @@ import type { TestRunnerConfig } from '@web/test-runner'; import { stat } from 'node:fs/promises'; -import { playwrightLauncher } from '@web/test-runner-playwright'; +import { chromeLauncher } from '@web/test-runner-chrome'; +import puppeteer from 'puppeteer'; import { summaryReporter, defaultReporter } from '@web/test-runner'; import { junitReporter } from '@web/test-runner-junit-reporter'; import { a11ySnapshotPlugin } from '@web/test-runner-commands/plugins'; @@ -84,15 +85,22 @@ export function pfeTestRunnerConfig(opts: PfeTestRunnerConfigOptions): TestRunne '!**/_site/**/*', ], browsers: [ - playwrightLauncher({ + chromeLauncher({ + puppeteer: puppeteer as never, + launchOptions: { + args: process.env.CI ? ['--no-sandbox'] : [], + }, createBrowserContext: async ({ browser }) => { - const context = await browser.newContext(); - // grant permissions to access the users clipboard - await context.grantPermissions(['clipboard-read', 'clipboard-write']); + const context = await browser.defaultBrowserContext(); + await context.overridePermissions('http://localhost', [ + 'clipboard-read', + 'clipboard-write', + ]); return context; }, }), ], + concurrency: 1, testFramework: { config: { ui: 'bdd', diff --git a/tools/pfe-tools/test/create-fixture.ts b/tools/pfe-tools/test/create-fixture.ts index 31f586eaa2..6a7c2efd0d 100644 --- a/tools/pfe-tools/test/create-fixture.ts +++ b/tools/pfe-tools/test/create-fixture.ts @@ -1,6 +1,6 @@ import type { TemplateResult } from 'lit'; import { chai, fixtureCleanup, fixture } from '@open-wc/testing'; -// @ts-ignore: colorjs.io types not resolved with Node moduleResolution on Windows CI +// @ts-expect-error: colorjs.io types not resolved with Node moduleResolution on Windows CI import Color from 'colorjs.io'; /** diff --git a/tools/pfe-tools/test/utils.ts b/tools/pfe-tools/test/utils.ts index 29f4d0dc52..99b748caa3 100644 --- a/tools/pfe-tools/test/utils.ts +++ b/tools/pfe-tools/test/utils.ts @@ -1,6 +1,28 @@ -import { sendMouse } from '@web/test-runner-commands'; +import { sendKeys, sendMouse } from '@web/test-runner-commands'; import type { ReactiveElement } from 'lit'; +const MODIFIERS = ['Shift', 'Control', 'Alt', 'Meta'] as const; + +/** + * Press a key or key combination (e.g. 'Shift+Tab'). + * Decomposes modifier combos for Puppeteer compatibility. + */ +export async function press(key: string): Promise { + const parts = key.split('+'); + const mainKey = parts.pop()!; + type Mod = typeof MODIFIERS[number]; + const isMod = (m: string): m is Mod => + MODIFIERS.includes(m as Mod); + const mods = parts.filter(isMod); + for (const mod of mods) { + await sendKeys({ down: mod }); + } + await sendKeys({ press: mainKey }); + for (const mod of [...mods].reverse()) { + await sendKeys({ up: mod }); + } +} + export type Position = [x: number, y: number]; /** diff --git a/web-dev-server.config.js b/web-dev-server.config.js index c71fff3535..07e31c4f30 100644 --- a/web-dev-server.config.js +++ b/web-dev-server.config.js @@ -1,7 +1,4 @@ -import { - pfeDevServerConfig, - getPatternflyIconNodemodulesImports, -} from '@patternfly/pfe-tools/dev-server/config.js'; +import { pfeDevServerConfig } from '@patternfly/pfe-tools/dev-server/config.js'; import { makeDemoEnv } from '@patternfly/pfe-tools/environment.js'; import { writeFile, mkdir } from 'node:fs/promises';