Skip to content

Commit 115d3d8

Browse files
rerun failed
1 parent 5bcdd96 commit 115d3d8

File tree

9 files changed

+249
-138
lines changed

9 files changed

+249
-138
lines changed

.github/workflows/testcafe_tests.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,21 @@ jobs:
5454
shell: bash
5555
run: |
5656
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
57+
pnpm config set network-concurrency 16
5758
5859
- uses: actions/cache@v4
5960
name: Setup pnpm cache
6061
with:
6162
path: |
6263
${{ env.STORE_PATH }}
6364
.nx/cache
65+
node_modules/.cache
6466
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
6567
restore-keys: |
6668
${{ runner.os }}-pnpm-store
6769
6870
- name: Install dependencies
69-
run: pnpm install --frozen-lockfile
71+
run: pnpm install --frozen-lockfile --prefer-offline
7072

7173
- name: Build
7274
shell: bash
@@ -157,19 +159,21 @@ jobs:
157159
shell: bash
158160
run: |
159161
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
162+
pnpm config set network-concurrency 16
160163
161164
- uses: actions/cache@v4
162165
name: Setup pnpm cache
163166
with:
164167
path: |
165168
${{ env.STORE_PATH }}
166169
.nx/cache
170+
node_modules/.cache
167171
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
168172
restore-keys: |
169173
${{ runner.os }}-pnpm-store
170174
171175
- name: Install dependencies
172-
run: pnpm install --frozen-lockfile
176+
run: pnpm install --frozen-lockfile --prefer-offline
173177

174178
- name: Run TestCafe tests
175179
working-directory: ./e2e/testcafe-devextreme

e2e/testcafe-devextreme/.testcaferc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"screenshots": {
33
"path": "./",
44
"takeOnFails": true,
5+
"thumbnails": false,
56
"pathPattern": "/artifacts/failedtests/${TEST}"
67
},
78
"screenshots-comparer": {

e2e/testcafe-devextreme/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"mockdate": "3.0.5",
2121
"nconf": "0.12.1",
2222
"testcafe": "3.7.2",
23+
"testcafe-reporter-spec-time": "4.0.0",
2324
"ts-node": "10.9.2",
2425
"eslint": "catalog:",
2526
"@eslint/eslintrc": "catalog:",

e2e/testcafe-devextreme/runner.ts

Lines changed: 152 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getCurrentTheme } from './helpers/themeUtils';
1515

1616
const LAUNCH_RETRY_ATTEMPTS = 5;
1717
const LAUNCH_RETRY_TIMEOUT = 10000;
18+
const FAILED_TESTS_RETRY_ATTEMPTS = 3;
1819

1920
const wait = async (
2021
timeout: number,
@@ -55,6 +56,7 @@ interface ParsedArgs {
5556
shadowDom: boolean;
5657
skipUnstable: boolean;
5758
disableScreenshots: boolean;
59+
retryFailed: boolean;
5860
}
5961

6062
const TESTCAFE_CONFIG: Partial<TestCafeConfigurationOptions> = {
@@ -101,7 +103,7 @@ function getArgs(): ParsedArgs {
101103
concurrency: 0,
102104
browsers: 'chrome',
103105
test: '',
104-
reporter: process.env.CI === 'true' ? 'list' : 'spec',
106+
reporter: 'spec-time',
105107
componentFolder: '',
106108
file: '*',
107109
cache: true,
@@ -112,6 +114,7 @@ function getArgs(): ParsedArgs {
112114
shadowDom: false,
113115
skipUnstable: true,
114116
disableScreenshots: false,
117+
retryFailed: true,
115118
},
116119
}) as ParsedArgs;
117120
}
@@ -157,77 +160,89 @@ async function main() {
157160
// eslint-disable-next-line no-console
158161
console.info('Browsers:', browsers);
159162

160-
const runner: Runner = testCafe.createRunner()
161-
.browsers(browsers)
162-
.reporter(reporter)
163-
.src([`./tests/${componentFolder}/${file}.ts`]);
163+
const failedTests: Set<string> = new Set();
164164

165-
runner.compilerOptions({
166-
typescript: {
167-
customCompilerModulePath: '../../node_modules/typescript',
168-
},
169-
});
170-
171-
runner.concurrency(args.concurrency || 4);
165+
const createRunner = (filterByFailedTests = false) => {
166+
const runner: Runner = testCafe!.createRunner()
167+
.browsers(browsers)
168+
.reporter(reporter)
169+
.src([`./tests/${componentFolder}/${file}.ts`]);
172170

173-
const filters: FilterFunction[] = [];
171+
runner.compilerOptions({
172+
typescript: {
173+
customCompilerModulePath: '../../node_modules/typescript',
174+
},
175+
});
174176

175-
if (indices) {
176-
const [current, total] = indices.split(/_|of|\\|\//ig).map((x) => +x);
177-
const fixtures = globSync([`./tests/${componentFolder}/*.ts`]);
178-
const fixtureChunks = split(fixtures, total);
179-
const targetFixtureChunk = fixtureChunks[current - 1] ?? [];
180-
const targetFixtureChunkSet = new Set(targetFixtureChunk);
177+
runner.concurrency(filterByFailedTests ? 1 : (args.concurrency || 4));
178+
179+
const filters: FilterFunction[] = [];
180+
181+
if (indices) {
182+
const [current, total] = indices.split(/_|of|\\|\//ig).map((x) => +x);
183+
const fixtures = globSync([`./tests/${componentFolder}/*.ts`]);
184+
const fixtureChunks = split(fixtures, total);
185+
const targetFixtureChunk = fixtureChunks[current - 1] ?? [];
186+
const targetFixtureChunkSet = new Set(targetFixtureChunk);
187+
188+
if (!filterByFailedTests) {
189+
/* eslint-disable no-console */
190+
console.info(' === test run config ===');
191+
console.info(` > indices: current = ${current} | total = ${total}`);
192+
console.info(' > glob: ', [`./tests/${componentFolder}/*.ts`]);
193+
console.info(' > all fixtures: ', fixtureChunks);
194+
console.info(' > fixtures: ', targetFixtureChunk, '\n');
195+
/* eslint-enable no-console */
196+
}
181197

182-
/* eslint-disable no-console */
183-
console.info(' === test run config ===');
184-
console.info(` > indices: current = ${current} | total = ${total}`);
185-
console.info(' > glob: ', [`./tests/${componentFolder}/*.ts`]);
186-
console.info(' > all fixtures: ', fixtureChunks);
187-
console.info(' > fixtures: ', targetFixtureChunk, '\n');
188-
/* eslint-enable no-console */
198+
filters.push((
199+
_testName: string,
200+
_fixtureName: string,
201+
fixturePath: string,
202+
) => {
203+
const testPath = fixturePath.split('/testcafe-devextreme/')[1];
204+
return targetFixtureChunkSet.has(testPath);
205+
});
206+
}
189207

190-
filters.push((
191-
_testName: string,
192-
_fixtureName: string,
193-
fixturePath: string,
194-
) => {
195-
const testPath = fixturePath.split('/testcafe-devextreme/')[1];
196-
return targetFixtureChunkSet.has(testPath);
197-
});
198-
}
208+
if (testName) {
209+
filters.push((name: string) => name === testName);
210+
}
199211

200-
if (testName) {
201-
filters.push((name: string) => name === testName);
202-
}
212+
if (filterByFailedTests && failedTests.size > 0) {
213+
filters.push((name: string) => failedTests.has(name));
214+
}
203215

204-
if (args.skipUnstable) {
205-
filters.push((
206-
_testName: string,
207-
_fixtureName: string,
208-
_fixturePath: string,
209-
testMeta?: any,
210-
) => !(testMeta)?.unstable);
211-
}
216+
if (args.skipUnstable) {
217+
filters.push((
218+
_testName: string,
219+
_fixtureName: string,
220+
_fixturePath: string,
221+
testMeta?: any,
222+
) => !(testMeta)?.unstable);
223+
}
212224

213-
if (filters.length) {
214-
runner.filter((...filterArgs: Parameters<FilterFunction>) => {
215-
// eslint-disable-next-line @typescript-eslint/prefer-for-of
216-
for (let i = 0; i < filters.length; i += 1) {
217-
if (!filters[i](...filterArgs)) {
218-
return false;
225+
if (filters.length) {
226+
runner.filter((...filterArgs: Parameters<FilterFunction>) => {
227+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
228+
for (let i = 0; i < filters.length; i += 1) {
229+
if (!filters[i](...filterArgs)) {
230+
return false;
231+
}
219232
}
220-
}
221-
return true;
222-
});
223-
}
233+
return true;
234+
});
235+
}
224236

225-
if (args.cache) {
226-
(runner as any).cache = args.cache;
227-
}
237+
if (args.cache) {
238+
(runner as any).cache = args.cache;
239+
}
240+
241+
return runner;
242+
};
228243

229244
const runOptions: RunOptions = {
230-
quarantineMode: { successThreshold: 1, attemptLimit: 2 },
245+
quarantineMode: false,
231246
// @ts-expect-error ts-error
232247
hooks: {
233248
test: {
@@ -266,6 +281,14 @@ async function main() {
266281
},
267282
after: async (t: TestController) => {
268283
await clearTestPage(t);
284+
285+
if (args.retryFailed) {
286+
// @ts-expect-error ts-errors
287+
const { test, errs } = t.testRun;
288+
if (errs && errs.length > 0) {
289+
failedTests.add(test.name);
290+
}
291+
}
269292
},
270293
},
271294
},
@@ -275,7 +298,75 @@ async function main() {
275298
runOptions.disableScreenshots = true;
276299
}
277300

278-
const failedCount = await retry(() => runner.run(runOptions), LAUNCH_RETRY_ATTEMPTS);
301+
// First run - all tests
302+
const runner = createRunner(false);
303+
let failedCount = await retry(() => runner.run(runOptions), LAUNCH_RETRY_ATTEMPTS);
304+
305+
// Retry failed tests multiple times if enabled and there are failures
306+
if (args.retryFailed && failedTests.size > 0 && failedCount > 0) {
307+
const initialFailedCount = failedTests.size;
308+
let attemptsLeft = FAILED_TESTS_RETRY_ATTEMPTS;
309+
310+
while (attemptsLeft > 0 && failedTests.size > 0 && failedCount > 0) {
311+
const attemptNumber = FAILED_TESTS_RETRY_ATTEMPTS - attemptsLeft + 1;
312+
313+
/* eslint-disable no-console */
314+
console.info('\n');
315+
console.info('='.repeat(60));
316+
console.info(`RETRY ATTEMPT ${attemptNumber}/${FAILED_TESTS_RETRY_ATTEMPTS}`);
317+
console.info(`Retrying ${failedTests.size} failed test(s)`);
318+
console.info('='.repeat(60));
319+
console.info('Failed tests:');
320+
failedTests.forEach((failedTestName) => console.info(` - ${failedTestName}`));
321+
console.info('='.repeat(60));
322+
console.info('\n');
323+
/* eslint-enable no-console */
324+
325+
const testsToRetry = new Set(failedTests);
326+
failedTests.clear();
327+
328+
const retryRunner = createRunner(true);
329+
failedCount = await retry(
330+
() => retryRunner.run(runOptions),
331+
LAUNCH_RETRY_ATTEMPTS,
332+
);
333+
334+
attemptsLeft -= 1;
335+
336+
/* eslint-disable no-console */
337+
console.info('\n');
338+
console.info('='.repeat(60));
339+
console.info(`ATTEMPT ${attemptNumber} RESULTS`);
340+
console.info('='.repeat(60));
341+
console.info(`Tests retried: ${testsToRetry.size}`);
342+
console.info(`Still failing: ${failedCount}`);
343+
console.info(`Passed on this attempt: ${testsToRetry.size - failedCount}`);
344+
console.info('='.repeat(60));
345+
console.info('\n');
346+
/* eslint-enable no-console */
347+
348+
if (failedCount === 0) {
349+
/* eslint-disable no-console */
350+
console.info('✅ All previously failed tests now pass!');
351+
console.info('\n');
352+
/* eslint-enable no-console */
353+
break;
354+
}
355+
}
356+
357+
/* eslint-disable no-console */
358+
console.info('\n');
359+
console.info('='.repeat(60));
360+
console.info('FINAL RETRY RESULTS');
361+
console.info('='.repeat(60));
362+
console.info(`Initially failed: ${initialFailedCount}`);
363+
console.info(`Total retry attempts used: ${FAILED_TESTS_RETRY_ATTEMPTS - attemptsLeft}`);
364+
console.info(`Final failing count: ${failedCount}`);
365+
console.info(`Successfully recovered: ${initialFailedCount - failedCount}`);
366+
console.info('='.repeat(60));
367+
console.info('\n');
368+
/* eslint-enable no-console */
369+
}
279370

280371
await testCafe.close();
281372

e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const CLASS = ClassNames;
2323

2424
const getOnKeyDownCallCount = ClientFunction(() => (window as any).onKeyDownCallCount);
2525

26-
fixture`Keyboard Navigation - common`
26+
fixture.disablePageReloads`Keyboard Navigation - common`
2727
.page(url(__dirname, '../../../container.html'));
2828

2929
test('Changing keyboardNavigation options should not invalidate the entire content (T1197829)', async (t) => {

e2e/testcafe-devextreme/tests/dataGrid/common/rowDragging.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ test('The cross-component drag and drop rows should not block rows', async (t) =
369369

370370
test('Virtual rendering during auto scrolling should not cause errors in onDragChange', async (t) => {
371371
const dataGrid = new DataGrid('#container');
372-
await t.drag(dataGrid.getDataRow(0).getDragCommand(), 0, 100, { speed: 0.01 });
372+
await t.drag(dataGrid.getDataRow(0).getDragCommand(), 0, 100, { speed: 0.1 });
373373

374374
const lastRow = dataGrid.getDataRow(9);
375375

@@ -414,7 +414,7 @@ test('Virtual rendering during auto scrolling should not cause errors in onDragC
414414
// T1078513
415415
test('Headers should not be hidden during auto scrolling when virtual scrollling is specified', async (t) => {
416416
const dataGrid = new DataGrid('#container');
417-
await t.drag(dataGrid.getDataRow(0).getDragCommand(), 0, 90, { speed: 0.01 });
417+
await t.drag(dataGrid.getDataRow(0).getDragCommand(), 0, 90, { speed: 0.1 });
418418

419419
const headerRow = dataGrid.getHeaders().getHeaderRow(0).element;
420420

@@ -470,7 +470,7 @@ test('Headers should not be hidden during auto scrolling when virtual scrollling
470470
// T1078513
471471
test('Footer should not be hidden during auto scrolling when virtual scrollling is specified', async (t) => {
472472
const dataGrid = new DataGrid('#container');
473-
await t.drag(dataGrid.getDataRow(0).getDragCommand(), 0, 90, { speed: 0.01 });
473+
await t.drag(dataGrid.getDataRow(0).getDragCommand(), 0, 90, { speed: 0.1 });
474474

475475
const footerRow = dataGrid.getFooterRow();
476476

@@ -578,7 +578,7 @@ test.meta({ unstable: true })('Dragging with scrolling should be prevented by e.
578578

579579
await MouseUpEvents.disable(MouseAction.dragToOffset);
580580

581-
await t.drag(dataGrid.getDataRow(98).getDragCommand(), 0, -180, { speed: 0.01 });
581+
await t.drag(dataGrid.getDataRow(98).getDragCommand(), 0, -180, { speed: 0.1 });
582582

583583
await t.expect(Selector('.dx-sortable-placeholder').visible).notOk();
584584

e2e/testcafe-devextreme/tests/editors/loadIndIcator/common.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createScreenshotsComparer } from 'devextreme-screenshot-comparer';
2-
import LoadIndicator from 'devextreme-testcafe-models/loadindicator';
32
import url from '../../../helpers/getPageUrl';
43
import { createWidget } from '../../../helpers/createWidget';
54
import { testScreenshot } from '../../../helpers/themeUtils';

0 commit comments

Comments
 (0)