Skip to content

Commit 698546c

Browse files
committed
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>
1 parent 875c80b commit 698546c

11 files changed

Lines changed: 121 additions & 194 deletions

File tree

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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-back-to-top/test/pf-back-to-top.spec.ts

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import { setViewport, sendKeys } from '@web/test-runner-commands';
55
import { allUpdates } from '@patternfly/pfe-tools/test/utils.js';
66

77
import { PfV5BackToTop } from '../pf-v5-back-to-top.js';
8-
import { type A11yTreeSnapshot, a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js';
9-
10-
const takeProps = (props: string[]) => (obj: object) =>
11-
Object.fromEntries(Object.entries(obj).filter(([k]) => props.includes(k)));
8+
import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js';
129

1310
describe('<pf-v5-back-to-top>', function() {
1411
it('imperatively instantiates', function() {
@@ -33,7 +30,6 @@ describe('<pf-v5-back-to-top>', function() {
3330

3431
describe('when rendered in a viewport with a height smaller then content length', function() {
3532
let element: PfV5BackToTop;
36-
let snapshot: A11yTreeSnapshot;
3733

3834
beforeEach(async function() {
3935
await setViewport({ width: 320, height: 640 });
@@ -46,30 +42,29 @@ describe('<pf-v5-back-to-top>', function() {
4642
</div>
4743
`);
4844
element = container.querySelector('pf-v5-back-to-top')!;
49-
snapshot = await a11ySnapshot();
50-
5145
await allUpdates(element);
5246
});
5347

54-
it('should be hidden on init', function() {
55-
const { children } = snapshot;
56-
expect(children).to.be.undefined;
48+
it('should be hidden on init', async function() {
49+
const snapshot = await a11ySnapshot();
50+
expect(snapshot).to.not.axContainRole('link');
5751
});
5852

59-
it('should not be accessible', function() {
60-
expect(snapshot.children).to.be.undefined;
53+
it('should not be accessible', async function() {
54+
const snapshot = await a11ySnapshot();
55+
expect(snapshot).to.not.axContainName('Back to top');
6156
});
6257

6358
describe('when scrolled 401px', function() {
6459
beforeEach(async function() {
6560
window.scrollTo({ top: 401, behavior: 'instant' });
6661
await nextFrame();
6762
await allUpdates(element);
68-
snapshot = await a11ySnapshot();
6963
});
7064

71-
it('should be visible', function() {
72-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]);
65+
it('should be visible', async function() {
66+
expect(await a11ySnapshot())
67+
.to.axContainQuery({ role: 'link', name: 'Back to top' });
7368
});
7469

7570
it('should be accessible', async function() {
@@ -95,11 +90,11 @@ describe('<pf-v5-back-to-top>', function() {
9590
await nextFrame();
9691
element.alwaysVisible = true;
9792
await allUpdates(element);
98-
snapshot = await a11ySnapshot();
9993
});
10094

101-
it('should be visible', function() {
102-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]);
95+
it('should be visible', async function() {
96+
expect(await a11ySnapshot())
97+
.to.axContainQuery({ role: 'link', name: 'Back to top' });
10398
});
10499

105100
it('should be accessible', async function() {
@@ -122,32 +117,29 @@ describe('<pf-v5-back-to-top>', function() {
122117
beforeEach(async function() {
123118
element.scrollDistance = 1000;
124119
await allUpdates(element);
125-
snapshot = await a11ySnapshot();
126120
});
127121

128-
it('should be hidden', function() {
129-
const { children } = snapshot;
130-
expect(children).to.be.undefined;
122+
it('should be hidden', async function() {
123+
expect(await a11ySnapshot()).to.not.axContainRole('link');
131124
});
132125

133126
describe('when scrolled 1001px', function() {
134127
beforeEach(async function() {
135128
window.scrollTo({ top: 1001, behavior: 'instant' });
136129
await nextFrame();
137130
await allUpdates(element);
138-
snapshot = await a11ySnapshot();
139131
});
140132

141-
it('should be visible', function() {
142-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]);
133+
it('should be visible', async function() {
134+
expect(await a11ySnapshot())
135+
.to.axContainQuery({ role: 'link', name: 'Back to top' });
143136
});
144137
});
145138
});
146139
});
147140

148141
describe('when rendered in an element with an overflowed height', function() {
149142
let element: PfV5BackToTop;
150-
let snapshot: A11yTreeSnapshot;
151143

152144
beforeEach(async function() {
153145
window.scrollTo({ top: 0, behavior: 'instant' });
@@ -160,13 +152,11 @@ describe('<pf-v5-back-to-top>', function() {
160152
`);
161153
element = container.querySelector('pf-v5-back-to-top')!;
162154
await allUpdates(element);
163-
164-
snapshot = await a11ySnapshot({ selector: 'pf-v5-back-to-top' });
165155
});
166156

167-
it('should be hidden on init', function() {
168-
const { children } = snapshot;
169-
expect(children).to.be.undefined;
157+
it('should be hidden on init', async function() {
158+
const snapshot = await a11ySnapshot({ selector: 'pf-v5-back-to-top' });
159+
expect(snapshot?.children).to.not.be.ok;
170160
});
171161

172162
describe('when scrolled 401px', function() {
@@ -176,18 +166,17 @@ describe('<pf-v5-back-to-top>', function() {
176166
scrollableElement.dispatchEvent(new Event('scroll'));
177167
await nextFrame();
178168
await allUpdates(element);
179-
snapshot = await a11ySnapshot();
180169
});
181170

182-
it('should be visible', function() {
183-
expect(snapshot.children?.at(0)?.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]);
171+
it('should be visible', async function() {
172+
expect(await a11ySnapshot())
173+
.to.axContainQuery({ role: 'link', name: 'Back to top' });
184174
});
185175
});
186176
});
187177

188178
describe('when no text is provided', function() {
189179
let element: PfV5BackToTop;
190-
let snapshot: A11yTreeSnapshot;
191180

192181
describe('as a link', function() {
193182
beforeEach(async function() {
@@ -209,11 +198,11 @@ describe('<pf-v5-back-to-top>', function() {
209198
window.scrollTo({ top: 401, behavior: 'instant' });
210199
await nextFrame();
211200
await allUpdates(element);
212-
snapshot = await a11ySnapshot();
213201
});
214202

215-
it('should have a label of "Back to top"', function() {
216-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Back to top' }]);
203+
it('should have a label of "Back to top"', async function() {
204+
expect(await a11ySnapshot())
205+
.to.axContainQuery({ role: 'link', name: 'Back to top' });
217206
});
218207
});
219208
});
@@ -238,19 +227,18 @@ describe('<pf-v5-back-to-top>', function() {
238227
window.scrollTo({ top: 401, behavior: 'instant' });
239228
await nextFrame();
240229
await allUpdates(element);
241-
snapshot = await a11ySnapshot();
242230
});
243231

244-
it('should have a label of "Back to top"', function() {
245-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'button', name: 'Back to top' }]);
232+
it('should have a label of "Back to top"', async function() {
233+
expect(await a11ySnapshot())
234+
.to.axContainQuery({ role: 'button', name: 'Back to top' });
246235
});
247236
});
248237
});
249238
});
250239

251240
describe('when a label is provided', function() {
252241
let element: PfV5BackToTop;
253-
let snapshot: A11yTreeSnapshot;
254242

255243
describe('as a link', function() {
256244
beforeEach(async function() {
@@ -272,11 +260,11 @@ describe('<pf-v5-back-to-top>', function() {
272260
window.scrollTo({ top: 401, behavior: 'instant' });
273261
await nextFrame();
274262
await allUpdates(element);
275-
snapshot = await a11ySnapshot();
276263
});
277264

278-
it('should have a label of "Return to top"', function() {
279-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'link', name: 'Return to top' }]);
265+
it('should have a label of "Return to top"', async function() {
266+
expect(await a11ySnapshot())
267+
.to.axContainQuery({ role: 'link', name: 'Return to top' });
280268
});
281269
});
282270
});
@@ -301,11 +289,11 @@ describe('<pf-v5-back-to-top>', function() {
301289
window.scrollTo({ top: 401, behavior: 'instant' });
302290
await nextFrame();
303291
await allUpdates(element);
304-
snapshot = await a11ySnapshot();
305292
});
306293

307-
it('should have a label of "Return to top"', function() {
308-
expect(snapshot.children?.map(takeProps(['name', 'role']))).to.deep.equal([{ role: 'button', name: 'Return to top' }]);
294+
it('should have a label of "Return to top"', async function() {
295+
expect(await a11ySnapshot())
296+
.to.axContainQuery({ role: 'button', name: 'Return to top' });
309297
});
310298
});
311299
});

elements/pf-v5-chip/test/pf-chip-group.spec.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('<pf-v5-chip-group>', async function() {
7171
beforeEach(updateComplete);
7272
it('should show all chips', async function() {
7373
const snapshot = await a11ySnapshot();
74-
expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4);
74+
expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(4);
7575
});
7676
it('should show collapse button', async function() {
7777
const snapshot = await a11ySnapshot();
@@ -104,11 +104,8 @@ describe('<pf-v5-chip-group>', async function() {
104104

105105
beforeEach(updateComplete);
106106
it('should have close button', async function() {
107-
const snapshot = await a11ySnapshot();
108-
const last = snapshot.children?.at(-1);
109-
expect(last?.name).to.equal('Close');
110-
expect(last?.role).to.equal('button');
111-
expect(last?.description).to.not.be.ok;
107+
expect(await a11ySnapshot())
108+
.to.axContainQuery({ role: 'button', name: 'Close' });
112109
});
113110

114111
describe('clicking close button', function() {
@@ -118,7 +115,7 @@ describe('<pf-v5-chip-group>', async function() {
118115
beforeEach(updateComplete);
119116
it('should remove element', async function() {
120117
const snapshot = await a11ySnapshot();
121-
expect(snapshot.children).to.not.be.ok;
118+
expect(snapshot).to.not.axContainRole('button');
122119
});
123120
});
124121
});
@@ -146,8 +143,7 @@ describe('<pf-v5-chip-group>', async function() {
146143
});
147144

148145
it('has accessible label', function() {
149-
const [offscreen] = snapshot.children!;
150-
expect(offscreen?.name).to.equal('My Chip Group');
146+
expect(snapshot).to.axContainName('My Chip Group');
151147
});
152148

153149
it('is accessible', async function() {
@@ -185,7 +181,7 @@ describe('<pf-v5-chip-group>', async function() {
185181
});
186182
it('only 2 chips should be visible', async function() {
187183
const snapshot = await a11ySnapshot();
188-
expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(2);
184+
expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(2);
189185
});
190186
});
191187

@@ -206,7 +202,7 @@ describe('<pf-v5-chip-group>', async function() {
206202

207203
it('all 4 chips should be visible', async function() {
208204
const snapshot = await a11ySnapshot();
209-
expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4);
205+
expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(4);
210206
});
211207

212208
describe('keyboard navigating with arrow keys to third chip and pressing enter', function() {
@@ -218,7 +214,7 @@ describe('<pf-v5-chip-group>', async function() {
218214

219215
it('should remove third chip', async function() {
220216
const snapshot = await a11ySnapshot();
221-
expect(snapshot.children?.find(x => x.name === 'Chip 3')).to.not.be.ok;
217+
expect(snapshot).to.not.axContainName('Chip 3');
222218
});
223219

224220
it('should focus on close button', async function() {

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js';
33
import { PfV5Chip } from '../pf-v5-chip.js';
44
import { sendKeys } from '@web/test-runner-commands';
55

6-
import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js';
6+
import { a11ySnapshot, querySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js';
77
import { clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js';
88

99

@@ -47,17 +47,17 @@ describe('<pf-v5-chip>', async function() {
4747
beforeEach(() => element.focus());
4848
it('should focus', async function() {
4949
const snapshot = await a11ySnapshot();
50-
expect(snapshot.children?.at(0)?.name).to.equal(element.accessibleCloseLabel);
51-
expect(snapshot.children?.at(0)?.focused).to.be.true;
50+
const focused = querySnapshot(snapshot, { focused: true });
51+
expect(focused).to.have.property('name', element.accessibleCloseLabel);
5252
});
5353
});
5454

5555
describe('pressing Tab', function() {
5656
beforeEach(press('Tab'));
5757
it('should focus', async function() {
5858
const snapshot = await a11ySnapshot();
59-
expect(snapshot.children?.at(0)?.name).to.equal(element.accessibleCloseLabel);
60-
expect(snapshot.children?.at(0)?.focused).to.be.true;
59+
const focused = querySnapshot(snapshot, { focused: true });
60+
expect(focused).to.have.property('name', element.accessibleCloseLabel);
6161
});
6262

6363
describe('pressing Enter', async function() {
@@ -85,7 +85,7 @@ describe('<pf-v5-chip>', async function() {
8585

8686
it('should not have a close button', async function() {
8787
const snapshot = await a11ySnapshot();
88-
expect(snapshot.children?.find(x => x.name === 'Close')).to.not.be.ok;
88+
expect(snapshot).to.not.axContainName('Close');
8989
});
9090

9191
describe('calling focus', function() {
@@ -133,7 +133,7 @@ describe('<pf-v5-chip>', async function() {
133133

134134
it('should not have a button', async function() {
135135
const snapshot = await a11ySnapshot();
136-
expect(snapshot.children).to.be.undefined;
136+
expect(snapshot).to.not.axContainRole('button');
137137
});
138138
});
139139
});

0 commit comments

Comments
 (0)