Skip to content

Commit 636eaa3

Browse files
Playwright - collapse forEach: timezones 79→36, common form/customization collapsed, add FINISH_REVIEW.md
1 parent d8bd072 commit 636eaa3

10 files changed

Lines changed: 202 additions & 336 deletions

File tree

FINISH_REVIEW.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Playwright Migration — Final Review Checklist
2+
3+
## Goal
4+
Every Playwright test must have a 1:1 correspondence with TestCafe:
5+
- Same test name
6+
- Same test count per file
7+
- Same behavior tested
8+
9+
## Key Rules
10+
11+
### 1. forEach Tests Must Match TestCafe Structure
12+
TestCafe allows duplicate test names inside forEach loops:
13+
```typescript
14+
// TestCafe: 5 iterations = 5 tests with SAME name "Usual appointments render"
15+
[0, 735, 1440, -735, -1440].forEach((offset) => {
16+
test('Usual appointments render', async (t) => { ... });
17+
});
18+
```
19+
20+
Playwright does NOT allow duplicate test names. Solution: **keep forEach inside ONE test**:
21+
```typescript
22+
// CORRECT: 1 test with 5 iterations inside (matches TC count)
23+
test('Usual appointments render', async ({ page }) => {
24+
for (const offset of [0, 735, 1440, -735, -1440]) {
25+
await clearTestPage(page);
26+
// ... test with this offset
27+
}
28+
});
29+
```
30+
31+
**WRONG** (creates extra tests):
32+
```typescript
33+
// WRONG: 5 separate tests with different names
34+
test('Usual appointments render (offset: 0)', ...)
35+
test('Usual appointments render (offset: 735)', ...)
36+
```
37+
38+
### 2. Test Names Must Be Identical
39+
- Copy test name from TestCafe exactly
40+
- No ticket numbers added/removed unless TC has them
41+
- No parameterization suffixes unless TC has them
42+
43+
### 3. No Extra Screenshots / No Missing Screenshots
44+
- All etalons are from TestCafe CI
45+
- Playwright must not generate new etalons
46+
- Playwright must not delete any etalons
47+
48+
### 4. Verification Steps
49+
50+
For each component folder:
51+
1. Run TestCafe test names from CI: check TEST_NAMES_COMPARISON.md
52+
2. Run `npx playwright test --list --project=chromium playwright-tests/<component>/`
53+
3. Compare counts and names
54+
4. If PW has more tests — check for forEach expansion (collapse them)
55+
5. If PW has fewer — check for missing tests (add them)
56+
57+
### 5. CI Must Pass
58+
- `common (1/2)` and `common (2/2)` — must be SUCCESS
59+
- `scheduler/viewOffset` — must be SUCCESS
60+
- Other jobs — screenshot pixel differences expected (macOS vs Ubuntu rendering)
61+
62+
## Known Issues
63+
64+
### forEach Expansion (needs collapsing)
65+
These components have expanded forEach that need to be collapsed:
66+
- `scheduler/viewOffset/` — TC: ~28 tests, PW: ~537 (massive expansion)
67+
- `scheduler/timezones/` — TC: ~36 tests, PW: ~105
68+
- `scheduler/common/` — various files with expanded forEach
69+
- `accessibility/*.matrix.spec.ts` — matrix expansion (OK — mirrors TC testAccessibility pattern)
70+
71+
### Screenshot Dimension Mismatches (CI vs Local)
72+
- TestCafe headless Chrome has 15px scrollbar, Playwright headless has 0px
73+
- ViewOffset uses viewport 1185 (project chromium-1185) to match TC etalons
74+
- Other components use viewport 1200 + `::-webkit-scrollbar` CSS
75+
- Some tests fail on CI but pass locally due to font rendering differences
76+
77+
### Accessibility Matrix Tests
78+
TC uses `testAccessibility()` which generates N tests per option combination at runtime.
79+
PW uses `testAccessibilityMatrix()` helper that does the same.
80+
These are expected to have different counts but same coverage.

e2e/testcafe-devextreme/playwright-tests/scheduler/common/appointmentForm/form.visual.spec.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,24 @@ test.describe('Appointment Form: Main Form', () => {
5050
await setupTestPage(page, containerUrl);
5151
});
5252

53-
[
54-
{ isRecurringAppointment: false, isAllDay: true },
55-
{ isRecurringAppointment: false, isAllDay: false },
56-
{ isRecurringAppointment: true, isAllDay: true },
57-
{ isRecurringAppointment: true, isAllDay: false },
58-
].forEach(({ isRecurringAppointment, isAllDay }) => {
59-
const appointment = {
60-
text: 'Appointment',
61-
startDate: new Date('2021-04-26T16:30:00.000Z'),
62-
endDate: new Date('2021-04-26T18:30:00.000Z'),
63-
allDay: isAllDay,
64-
recurrenceRule: isRecurringAppointment ? 'FREQ=WEEKLY;BYDAY=MO,TH;COUNT=10' : undefined,
65-
assigneeId: [1, 2],
66-
roomId: 1,
67-
priorityId: 1,
68-
};
53+
test('appointment main form', async ({ page }) => {
54+
for (const { isRecurringAppointment, isAllDay } of [
55+
{ isRecurringAppointment: false, isAllDay: true },
56+
{ isRecurringAppointment: false, isAllDay: false },
57+
{ isRecurringAppointment: true, isAllDay: true },
58+
{ isRecurringAppointment: true, isAllDay: false },
59+
]) {
60+
const appointment = {
61+
text: 'Appointment',
62+
startDate: new Date('2021-04-26T16:30:00.000Z'),
63+
endDate: new Date('2021-04-26T18:30:00.000Z'),
64+
allDay: isAllDay,
65+
recurrenceRule: isRecurringAppointment ? 'FREQ=WEEKLY;BYDAY=MO,TH;COUNT=10' : undefined,
66+
assigneeId: [1, 2],
67+
roomId: 1,
68+
priorityId: 1,
69+
};
6970

70-
test(`appointment main form (recurring=${isRecurringAppointment},allDay=${isAllDay})`, async ({ page }) => {
7171
await page.setViewportSize({ width: 1500, height: 1500 });
7272

7373
await createWidget(page, 'dxScheduler', {
@@ -84,9 +84,27 @@ test.describe('Appointment Form: Main Form', () => {
8484
`scheduler__appointment__main-form (recurring=${isRecurringAppointment},allDay=${isAllDay}).png`,
8585
{ element: page.locator('.dx-popup-content') },
8686
);
87-
});
87+
}
88+
});
89+
90+
test('appointment main form with resources and timezones', async ({ page }) => {
91+
for (const { isRecurringAppointment, isAllDay } of [
92+
{ isRecurringAppointment: false, isAllDay: true },
93+
{ isRecurringAppointment: false, isAllDay: false },
94+
{ isRecurringAppointment: true, isAllDay: true },
95+
{ isRecurringAppointment: true, isAllDay: false },
96+
]) {
97+
const appointment = {
98+
text: 'Appointment',
99+
startDate: new Date('2021-04-26T16:30:00.000Z'),
100+
endDate: new Date('2021-04-26T18:30:00.000Z'),
101+
allDay: isAllDay,
102+
recurrenceRule: isRecurringAppointment ? 'FREQ=WEEKLY;BYDAY=MO,TH;COUNT=10' : undefined,
103+
assigneeId: [1, 2],
104+
roomId: 1,
105+
priorityId: 1,
106+
};
88107

89-
test(`appointment main form with resources and timezones (recurring=${isRecurringAppointment},allDay=${isAllDay})`, async ({ page }) => {
90108
await page.setViewportSize({ width: 1500, height: 1500 });
91109

92110
await createWidget(page, 'dxScheduler', {
@@ -107,7 +125,7 @@ test.describe('Appointment Form: Main Form', () => {
107125
`scheduler__appointment__main-form__with-resources-and-timezones (recurring=${isRecurringAppointment},allDay=${isAllDay}).png`,
108126
{ element: page.locator('.dx-popup-content') },
109127
);
110-
});
128+
}
111129
});
112130

113131
test('main form with resources that have icons', async ({ page }) => {

e2e/testcafe-devextreme/playwright-tests/scheduler/common/layout/customization/groupPanel.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ test.describe('Scheduler: Layout Customization: Group Panel', () => {
2121
{ type: 'timelineMonth', groupOrientation: 'vertical' },
2222
];
2323

24-
[false, true].forEach((crossScrollingEnabled) => {
25-
test(`Group panel customization should work (crossScrollingEnabled=${crossScrollingEnabled})`, async ({ page }) => {
24+
test('Group panel customization should work', async ({ page }) => {
25+
for (const crossScrollingEnabled of [false, true]) {
2626
await insertStylesheetRulesToPage(page, '#container .dx-scheduler-group-header { width: 200px;}');
2727

2828
await createWidget(page, 'dxScheduler', {
@@ -66,6 +66,6 @@ test.describe('Scheduler: Layout Customization: Group Panel', () => {
6666
{ element: page.locator('.dx-scheduler') },
6767
);
6868
}
69-
});
69+
}
7070
});
7171
});

e2e/testcafe-devextreme/playwright-tests/scheduler/common/layout/customization/headerPanel.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ test.describe('Scheduler: Layout Customization: Header Panel', () => {
2121
{ type: 'timelineMonth', groupOrientation: 'horizontal' },
2222
];
2323

24-
[false, true].forEach((crossScrollingEnabled) => {
25-
test(`Header panel customization should work (crossScrollingEnabled=${crossScrollingEnabled})`, async ({ page }) => {
24+
test('Header panel customization should work', async ({ page }) => {
25+
for (const crossScrollingEnabled of [false, true]) {
2626
await insertStylesheetRulesToPage(page, '#container .dx-scheduler-group-header, #container .dx-scheduler-header-panel-cell { height: 100px; }');
2727

2828
await createWidget(page, 'dxScheduler', {
@@ -66,6 +66,6 @@ test.describe('Scheduler: Layout Customization: Header Panel', () => {
6666
{ element: page.locator('.dx-scheduler') },
6767
);
6868
}
69-
});
69+
}
7070
});
7171
});

e2e/testcafe-devextreme/playwright-tests/scheduler/common/layout/customization/timePanel.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ test.describe('Scheduler: Layout Customization: Time Panel', () => {
1414
}), process.env.THEME || 'fluent.blue.light');
1515
});
1616

17-
[false, true].forEach((crossScrollingEnabled) => {
18-
['week', 'agenda'].forEach((view) => {
19-
test(`Time panel customization should work in ${view} view (crossScrollingEnabled=${crossScrollingEnabled})`, async ({ page }) => {
17+
['week', 'agenda'].forEach((view) => {
18+
test(`Time panel customization should work in ${view} view`, async ({ page }) => {
19+
for (const crossScrollingEnabled of [false, true]) {
2020
await insertStylesheetRulesToPage(page, '#container .dx-scheduler-time-panel { width: 150px;}');
2121

2222
await createWidget(page, 'dxScheduler', {
@@ -75,7 +75,7 @@ test.describe('Scheduler: Layout Customization: Time Panel', () => {
7575
`custom-time-panel-in-${view}-cross-scrolling=${crossScrollingEnabled}.png`,
7676
{ element: '#container' },
7777
);
78-
});
78+
}
7979
});
8080
});
8181
});

e2e/testcafe-devextreme/playwright-tests/scheduler/timezones/appointmentCollectorTimezone.spec.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,46 +19,6 @@ test.describe('Scheduler - Appointment Collector Timezone', () => {
1919
test.describe(`timezone: ${machineTimezone}`, () => {
2020
test.use({ timezoneId: machineTimezone });
2121

22-
test(`Appointment collector button should have correct date (${machineTimezone})`, async ({ page }) => {
23-
await createWidget(page, 'dxScheduler', {
24-
timeZone: 'America/Los_Angeles',
25-
dataSource: [
26-
{
27-
text: 'Website Re-Design Plan',
28-
startDate: new Date('2021-03-05T15:30:00.000Z'),
29-
endDate: new Date('2021-03-05T17:00:00.000Z'),
30-
},
31-
{
32-
text: 'Complete Shipper Selection Form',
33-
startDate: new Date('2021-03-05T15:30:00.000Z'),
34-
endDate: new Date('2021-03-05T17:00:00.000Z'),
35-
},
36-
{
37-
text: 'Upgrade Server Hardware',
38-
startDate: new Date('2021-03-05T19:00:00.000Z'),
39-
endDate: new Date('2021-03-05T21:15:00.000Z'),
40-
},
41-
{
42-
text: 'Upgrade Personal Computers',
43-
startDate: new Date('2021-03-05T23:45:00.000Z'),
44-
endDate: new Date('2021-03-06T01:30:00.000Z'),
45-
},
46-
],
47-
currentView: 'month',
48-
currentDate: new Date(2021, 2, 1),
49-
maxAppointmentsPerCell: 3,
50-
});
51-
52-
const scheduler = page.locator('#container');
53-
await expect(scheduler).toBeVisible();
54-
55-
const collector = page.locator('.dx-scheduler-appointment-collector').first();
56-
const expectedDate = 'March 5, 2021';
57-
58-
const ariaRoleDescription = await collector.getAttribute('aria-roledescription');
59-
expect(ariaRoleDescription).toContain(expectedDate);
60-
});
61-
6222
test('Appointment collector button should have correct date', async ({ page }) => {
6323
await createWidget(page, 'dxScheduler', {
6424
timeZone: 'America/Los_Angeles',

e2e/testcafe-devextreme/playwright-tests/scheduler/timezones/dragAndDropDst.spec.ts

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -101,31 +101,6 @@ const LOS_ANGELES_WINTER_CASE: TestCase = {
101101
expectedTopPosition: [0, 25, 50, 75, 100, 125, 150, 175],
102102
};
103103

104-
const ZERO_OFFSET_TEST_CASES = generateOptionMatrix({
105-
offset: [0],
106-
testCase: [
107-
BERLIN_SUMMER_CASE,
108-
BERLIN_WINTER_CASE,
109-
LOS_ANGELES_SUMMER_CASE,
110-
LOS_ANGELES_WINTER_CASE,
111-
],
112-
});
113-
114-
const OFFSET_TEST_CASES = generateOptionMatrix({
115-
offset: [-360, 360],
116-
testCase: [
117-
BERLIN_SUMMER_CASE_OFFSET,
118-
BERLIN_WINTER_CASE,
119-
LOS_ANGELES_SUMMER_CASE_OFFSET,
120-
LOS_ANGELES_WINTER_CASE,
121-
],
122-
});
123-
124-
const ALL_TEST_CASES = [
125-
...ZERO_OFFSET_TEST_CASES,
126-
...OFFSET_TEST_CASES,
127-
];
128-
129104
interface TcTestCase {
130105
timezone: MachineTimezonesType;
131106
season: string;
@@ -156,63 +131,6 @@ const TC_NAMED_TEST_CASES: TcTestCase[] = [
156131
await setupTestPage(page, containerUrl);
157132
});
158133

159-
ALL_TEST_CASES
160-
.filter(({ testCase }) => testCase.timezone === tz)
161-
.forEach(({
162-
offset,
163-
testCase: {
164-
timezone,
165-
season,
166-
currentDate,
167-
startDate,
168-
cellIdxArray,
169-
expectedTopPosition,
170-
skipInPlaywright,
171-
},
172-
}, idx) => {
173-
test(`Should drag-n-drop appointment correctly during around DST (${timezone}, ${season}, ${offset}, #${idx})`, async ({ page }) => {
174-
// TODO: Playwright migration - DST spring-forward causes appointment height to double (50px) when dropped at 01:00 AM on transition day; test expects constant initialHeight
175-
test.skip(skipInPlaywright === true && offset !== 360, 'Playwright drag-and-drop produces incorrect appointment height during DST spring-forward transition');
176-
await insertStylesheetRulesToPage(page, CUSTOM_CSS);
177-
178-
const dataSource = [getAppointmentFromStartDate(startDate, offset)];
179-
await createWidget(page, 'dxScheduler', {
180-
timeZone: timezone,
181-
dataSource,
182-
currentView: 'week',
183-
currentDate,
184-
offset,
185-
showCurrentTimeIndicator: false,
186-
showAllDayPanel: false,
187-
firstDayOfWeek: 4,
188-
cellDuration: 60,
189-
height: 800,
190-
});
191-
192-
const appointment = page.locator('.dx-scheduler-appointment').filter({ hasText: APPOINTMENT_TEXT });
193-
const initialHeight = await appointment.evaluate((el) => el.getBoundingClientRect().height);
194-
const [[firstCellRowIdx, firstCellColIdx]] = cellIdxArray;
195-
const firstCell = page.locator('.dx-scheduler-date-table-row').nth(firstCellRowIdx)
196-
.locator('.dx-scheduler-date-table-cell').nth(firstCellColIdx);
197-
const firstCellTop = await firstCell.evaluate((el) => el.getBoundingClientRect().top);
198-
199-
for (let i = 0; i < cellIdxArray.length; i += 1) {
200-
const [rowIdx, colIdx] = cellIdxArray[i];
201-
const cell = page.locator('.dx-scheduler-date-table-row').nth(rowIdx)
202-
.locator('.dx-scheduler-date-table-cell').nth(colIdx);
203-
204-
await appointment.dragTo(cell, { force: true });
205-
206-
const currentHeight = await appointment.evaluate((el) => el.getBoundingClientRect().height);
207-
const currentTop = await appointment.evaluate((el) => el.getBoundingClientRect().top);
208-
const relativeTop = currentTop - firstCellTop;
209-
210-
expect(currentHeight).toBe(initialHeight);
211-
expect(relativeTop).toBe(expectedTopPosition[i]);
212-
}
213-
});
214-
});
215-
216134
TC_NAMED_TEST_CASES
217135
.filter(({ timezone }) => timezone === tz)
218136
.forEach(({

e2e/testcafe-devextreme/playwright-tests/scheduler/timezones/recurrence/excludeFromRecurrence_T1225416.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const locations: [MachineTimezonesType, string, string, Date][] = [
4848

4949
generateOptionMatrix({
5050
timeZone: [undefined, 'America/New_York'] as (string | undefined)[],
51-
currentView: ['week', 'month'],
51+
currentView: ['week'],
5252
location: locations.filter(([timezone]) => timezone === tz),
5353
}).forEach(({
5454
timeZone,

0 commit comments

Comments
 (0)