apify-test-tools 0.10.2-beta.19 → 0.10.2-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.github/cli/apify-test-tools.mjs +114 -114
  2. package/.github/scripts/run-with-apify-tokens.mjs +6 -6
  3. package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +1 -1
  4. package/.github/workflows/public_platform-tests.yaml +6 -8
  5. package/.github/workflows/public_pr-build-test.yaml +11 -11
  6. package/.github/workflows/public_push-build-latest.yaml +1 -0
  7. package/CHANGELOG.md +1 -0
  8. package/CONTRIBUTING.md +1 -1
  9. package/README.md +10 -6
  10. package/bin/affected-actors.ts +20 -7
  11. package/bin/diff-changes.ts +40 -16
  12. package/bin/main.ts +12 -19
  13. package/bin/run-tests.ts +29 -4
  14. package/bin/test-files-glob.ts +126 -0
  15. package/bin/utils/config/load-config.ts +4 -1
  16. package/bin/utils/config/parser.ts +24 -1
  17. package/dist/bin/affected-actors.d.ts +13 -4
  18. package/dist/bin/affected-actors.d.ts.map +1 -1
  19. package/dist/bin/affected-actors.js +11 -4
  20. package/dist/bin/affected-actors.js.map +1 -1
  21. package/dist/bin/diff-changes.d.ts +12 -3
  22. package/dist/bin/diff-changes.d.ts.map +1 -1
  23. package/dist/bin/diff-changes.js +25 -12
  24. package/dist/bin/diff-changes.js.map +1 -1
  25. package/dist/bin/main.js +11 -17
  26. package/dist/bin/main.js.map +1 -1
  27. package/dist/bin/run-tests.d.ts +3 -3
  28. package/dist/bin/run-tests.d.ts.map +1 -1
  29. package/dist/bin/run-tests.js +24 -2
  30. package/dist/bin/run-tests.js.map +1 -1
  31. package/dist/bin/test-files-glob.d.ts +18 -0
  32. package/dist/bin/test-files-glob.d.ts.map +1 -0
  33. package/dist/bin/test-files-glob.js +108 -0
  34. package/dist/bin/test-files-glob.js.map +1 -0
  35. package/dist/bin/utils/config/load-config.d.ts +3 -0
  36. package/dist/bin/utils/config/load-config.d.ts.map +1 -1
  37. package/dist/bin/utils/config/load-config.js +3 -1
  38. package/dist/bin/utils/config/load-config.js.map +1 -1
  39. package/dist/bin/utils/config/parser.d.ts +9 -0
  40. package/dist/bin/utils/config/parser.d.ts.map +1 -1
  41. package/dist/bin/utils/config/parser.js +13 -1
  42. package/dist/bin/utils/config/parser.js.map +1 -1
  43. package/dist/test/bundle/cli-bundle.test.js +1 -1
  44. package/dist/test/bundle/cli-bundle.test.js.map +1 -1
  45. package/dist/test/e2e/cli-bundle.e2e.test.js +1 -1
  46. package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
  47. package/dist/test/unit/bin/actor-filtering.test.js +7 -0
  48. package/dist/test/unit/bin/actor-filtering.test.js.map +1 -1
  49. package/dist/test/unit/bin/affected-actors-cli.test.js +59 -2
  50. package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -1
  51. package/dist/test/unit/bin/affected-actors.test.js +18 -6
  52. package/dist/test/unit/bin/affected-actors.test.js.map +1 -1
  53. package/dist/test/unit/bin/diff-changes.test.js +44 -19
  54. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  55. package/dist/test/unit/bin/test-cli.test.js +34 -3
  56. package/dist/test/unit/bin/test-cli.test.js.map +1 -1
  57. package/dist/test/unit/bin/test-files-glob.test.d.ts +2 -0
  58. package/dist/test/unit/bin/test-files-glob.test.d.ts.map +1 -0
  59. package/dist/test/unit/bin/test-files-glob.test.js +102 -0
  60. package/dist/test/unit/bin/test-files-glob.test.js.map +1 -0
  61. package/dist/test/unit/bin/utils/config/parser.test.js +16 -1
  62. package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/docs/cli.md +46 -29
  65. package/docs/workflows.md +53 -20
  66. package/package.json +1 -1
  67. package/test/bundle/cli-bundle.test.ts +1 -1
  68. package/test/e2e/cli-bundle.e2e.test.ts +9 -5
  69. package/test/unit/bin/actor-filtering.test.ts +8 -0
  70. package/test/unit/bin/affected-actors-cli.test.ts +73 -3
  71. package/test/unit/bin/affected-actors.test.ts +49 -10
  72. package/test/unit/bin/diff-changes.test.ts +48 -19
  73. package/test/unit/bin/test-cli.test.ts +41 -3
  74. package/test/unit/bin/test-files-glob.test.ts +118 -0
  75. package/test/unit/bin/utils/config/parser.test.ts +24 -1
@@ -71,6 +71,10 @@ const changedActorsFor = async ({
71
71
  return options.actorConfigs.filter((config) => changed.includes(config));
72
72
  };
73
73
 
74
+ // The Actors whose platform tests run because of a test change
75
+ const testChangedActorsFor = async (options: Parameters<typeof getChangedActors>[0]): Promise<ActorConfig[]> =>
76
+ (await getChangedActors(options)).test;
77
+
74
78
  describe('getChangedActors', () => {
75
79
  beforeEach(() => {
76
80
  vi.restoreAllMocks();
@@ -404,34 +408,58 @@ describe('getChangedActors', () => {
404
408
  expect(result).toEqual([miniActor]);
405
409
  });
406
410
 
407
- it('test file listed in .dockerignore triggers a build when it matches the test files glob', async () => {
411
+ it('test file listed in .dockerignore is a test change when it matches the platform tests glob', async () => {
408
412
  vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue((filePath) => filePath.startsWith('test/'));
409
- const result = await changedActorsFor({
413
+ const options = {
410
414
  filepathsChanged: ['test/platform/daily/foo.test.ts'],
411
415
  actorConfigs: [miniActor],
412
416
  range,
413
- testFilesGlob: 'test/platform/**',
417
+ platformTestsGlob: 'test/platform/**',
418
+ };
419
+ expect(await testChangedActorsFor(options)).toEqual([miniActor]);
420
+ // A test change needs no build of its own, neither for a PR nor for a release
421
+ expect(await changedActorsFor({ ...options, isLatest: true })).toEqual([]);
422
+ });
423
+
424
+ it('a test change that is not listed in .dockerignore is still only a test change', async () => {
425
+ vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue(() => false);
426
+ const options = {
427
+ filepathsChanged: ['test/platform/utils.ts'],
428
+ actorConfigs: [miniActor],
429
+ range,
430
+ platformTestsGlob: 'test/platform/**',
431
+ };
432
+ expect(await testChangedActorsFor(options)).toEqual([miniActor]);
433
+ expect(await changedActorsFor({ ...options, isLatest: true })).toEqual([]);
434
+ });
435
+
436
+ it('lists an Actor with both source and test changes as functional and as test changed', async () => {
437
+ const result = await getChangedActors({
438
+ filepathsChanged: ['test/platform/daily/foo.test.ts', 'actors/foo_bar/src/main.ts'],
439
+ actorConfigs: [miniActor],
440
+ range,
441
+ platformTestsGlob: 'test/platform/**',
414
442
  });
415
- expect(result).toEqual([miniActor]);
443
+ expect(result).toStrictEqual({ functional: [miniActor], cosmetic: [], test: [miniActor] });
416
444
  });
417
445
 
418
- it('test file listed in .dockerignore stays ignored without a test files glob', async () => {
446
+ it('test file listed in .dockerignore stays ignored without a platform tests glob', async () => {
419
447
  vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue((filePath) => filePath.startsWith('test/'));
420
- const result = await changedActorsFor({
448
+ const result = await getChangedActors({
421
449
  filepathsChanged: ['test/platform/daily/foo.test.ts'],
422
450
  actorConfigs: [miniActor],
423
451
  range,
424
452
  });
425
- expect(result).toEqual([]);
453
+ expect(result).toStrictEqual({ functional: [], cosmetic: [], test: [] });
426
454
  });
427
455
 
428
- it('test files glob matches case-insensitively and does not bypass .dockerignore for other files', async () => {
456
+ it('platform tests glob matches case-insensitively and does not bypass .dockerignore for other files', async () => {
429
457
  vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue(() => true);
430
- const result = await changedActorsFor({
458
+ const result = await testChangedActorsFor({
431
459
  filepathsChanged: ['Test/Platform/Foo.test.ts'],
432
460
  actorConfigs: [miniActor],
433
461
  range,
434
- testFilesGlob: 'test/platform/**',
462
+ platformTestsGlob: 'test/platform/**',
435
463
  });
436
464
  expect(result).toEqual([miniActor]);
437
465
  expect(
@@ -439,7 +467,7 @@ describe('getChangedActors', () => {
439
467
  filepathsChanged: ['actors/foo_bar/node_modules/foo.js'],
440
468
  actorConfigs: [miniActor],
441
469
  range,
442
- testFilesGlob: 'test/platform/**',
470
+ platformTestsGlob: 'test/platform/**',
443
471
  }),
444
472
  ).toEqual([]);
445
473
  });
@@ -450,7 +478,7 @@ describe('getChangedActors', () => {
450
478
  filepathsChanged: ['test/platform/daily/foo.test.ts'],
451
479
  actorConfigs: [standaloneActor],
452
480
  range,
453
- testFilesGlob: 'test/platform/**',
481
+ platformTestsGlob: 'test/platform/**',
454
482
  });
455
483
  expect(result).toEqual([]);
456
484
  });
@@ -461,11 +489,11 @@ describe('getChangedActors', () => {
461
489
  ...standaloneActor,
462
490
  contextPaths: [...standaloneActor.contextPaths, new RelativeDir('test')],
463
491
  };
464
- const result = await changedActorsFor({
492
+ const result = await testChangedActorsFor({
465
493
  filepathsChanged: ['test/platform/standalone.test.ts'],
466
494
  actorConfigs: [actorWithTests],
467
495
  range,
468
- testFilesGlob: 'test/platform/**',
496
+ platformTestsGlob: 'test/platform/**',
469
497
  });
470
498
  expect(result).toEqual([actorWithTests]);
471
499
  });
@@ -602,6 +630,7 @@ describe('getChangedActors logging', () => {
602
630
  '[DIFF]: Changes specific to actor team/actor-b: actors/b/b-only.ts',
603
631
  '[DIFF]: Actors with functional changes: team/actor-a, team/actor-b',
604
632
  '[DIFF]: Actors with only cosmetic changes: none',
633
+ '[DIFF]: Actors with platform test changes: none',
605
634
  ]);
606
635
  });
607
636
 
@@ -614,7 +643,7 @@ describe('getChangedActors logging', () => {
614
643
  filepathsChanged: ['test/platform/standalone.test.ts'],
615
644
  actorConfigs: [standaloneActor],
616
645
  range,
617
- testFilesGlob: 'test/platform/**',
646
+ platformTestsGlob: 'test/platform/**',
618
647
  });
619
648
 
620
649
  expect(logger.warn).toHaveBeenCalledWith(outsideContextWarning('test/platform/standalone.test.ts'));
@@ -625,7 +654,7 @@ describe('getChangedActors logging', () => {
625
654
  filepathsChanged: ['test/platform/standalone.test.ts', 'standalone-actors/standalone/src/main.ts'],
626
655
  actorConfigs: [standaloneActor],
627
656
  range,
628
- testFilesGlob: 'test/platform/**',
657
+ platformTestsGlob: 'test/platform/**',
629
658
  });
630
659
 
631
660
  expect(result).toEqual([standaloneActor]);
@@ -637,7 +666,7 @@ describe('getChangedActors logging', () => {
637
666
  filepathsChanged: ['test/platform/foo.test.ts', 'test/platform/README.md', 'test/platform/CHANGELOG.md'],
638
667
  actorConfigs: [miniActor],
639
668
  range,
640
- testFilesGlob: 'test/platform/**',
669
+ platformTestsGlob: 'test/platform/**',
641
670
  });
642
671
 
643
672
  expect(logger.warn).not.toHaveBeenCalled();
@@ -672,12 +701,12 @@ describe('getChangedActors split by impact', () => {
672
701
  actorConfigs,
673
702
  range,
674
703
  });
675
- expect(result).toStrictEqual({ functional: [miniActor], cosmetic: [standaloneActor] });
704
+ expect(result).toStrictEqual({ functional: [miniActor], cosmetic: [standaloneActor], test: [] });
676
705
  });
677
706
 
678
707
  it('returns empty lists when nothing relevant changed', async () => {
679
708
  expect(
680
709
  await getChangedActors({ filepathsChanged: ['.vscode/settings.json'], actorConfigs, range }),
681
- ).toStrictEqual({ functional: [], cosmetic: [] });
710
+ ).toStrictEqual({ functional: [], cosmetic: [], test: [] });
682
711
  });
683
712
  });
@@ -45,6 +45,15 @@ test('owner/a: fails', () => {
45
45
  });
46
46
  `,
47
47
  );
48
+
49
+ // Outside test/platform, so the tests that run test/platform don't see them
50
+ for (const folder of ['with-pr', 'without-pr']) {
51
+ await mkdir(path.join(repoDir, 'test/glob', folder), { recursive: true });
52
+ await writeFile(
53
+ path.join(repoDir, 'test/glob', folder, `${folder}.test.ts`),
54
+ `import { test } from 'vitest';\n\ntest('owner/a: ${folder}', () => {});\n`,
55
+ );
56
+ }
48
57
  });
49
58
 
50
59
  afterAll(async () => {
@@ -92,17 +101,46 @@ test('owner/a: fails', () => {
92
101
  expect(JSON.parse(result.stdout)).toMatchObject({ success: false, summary: { passed: 1, failed: 1 } });
93
102
  });
94
103
 
95
- it('adds the reporters it is given to the default and JSON ones, also before the test files', () => {
104
+ it('passes vitest a folder whose files all match as one filter, and adds the reporters it is given', () => {
96
105
  const result = run(
97
- ['--log-level', 'debug', 'test', '--reporter', 'dot', '--reporter', 'verbose', 'test/platform'],
106
+ ['--log-level', 'debug', 'test', '--reporter', 'dot', '--reporter', 'verbose'],
98
107
  JSON.stringify([build]),
99
108
  );
109
+ // The default glob test/platform/** matches every file in test/platform, so vitest gets just that folder
100
110
  expect(result.stderr).toMatch(
101
- /Running vitest: \S+ run test\/platform --reporter=default --reporter=json --reporter=dot --reporter=verbose /,
111
+ /Running vitest: \S+ run \S+\/test\/platform\/ --reporter=default --reporter=json --reporter=dot --reporter=verbose /,
102
112
  );
103
113
  expect(JSON.parse(result.stdout)).toMatchObject({ summary: { passed: 1, failed: 1 } });
104
114
  });
105
115
 
116
+ it('runs only the test files that --test-files-glob matches from the repo root', () => {
117
+ const result = run(['test', '--test-files-glob', 'test/glob/{with-pr,other}/**'], JSON.stringify([build]));
118
+ expect(result.status, result.stderr).toBe(0);
119
+ expect(result.stderr).toContain('Running the 1 file(s) that match --test-files-glob');
120
+ expect(JSON.parse(result.stdout)).toMatchObject({
121
+ success: true,
122
+ summary: { total: 1, passed: 1 },
123
+ tests: [{ name: 'owner/a: with-pr', file: 'test/glob/with-pr/with-pr.test.ts' }],
124
+ });
125
+ });
126
+
127
+ it('fails when --test-files-glob matches no file instead of running every test', () => {
128
+ // Relative to test/platform, the way the workflows took it before
129
+ const result = run(['test', '--test-files-glob', 'hourly'], JSON.stringify([build]));
130
+ expect(result.status).toBe(1);
131
+ expect(result.stderr).toContain('No files match --test-files-glob "hourly"');
132
+ expect(result.stdout).toBe('');
133
+ });
134
+
135
+ it('rejects test paths given the old way, as arguments, instead of running every test', () => {
136
+ const result = run(['test', 'test/platform/core'], JSON.stringify([build]));
137
+ expect(result.status).toBe(1);
138
+ // The help that comes with it shows --test-files-glob
139
+ expect(result.stderr).toContain('Unknown command: test/platform/core');
140
+ expect(result.stderr).toContain('--test-files-glob');
141
+ expect(result.stdout).toBe('');
142
+ });
143
+
106
144
  it('fails without TESTER_APIFY_TOKEN instead of passing with every test skipped', () => {
107
145
  const result = run(['test'], JSON.stringify([build]), repoDir, { ...env, TESTER_APIFY_TOKEN: '' });
108
146
  expect(result.status).toBe(1);
@@ -0,0 +1,118 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6
+
7
+ import { createTestFilesMatcher, resolveTestFilesGlob } from '../../../bin/test-files-glob.js';
8
+
9
+ describe('createTestFilesMatcher', () => {
10
+ it('matches repo-root-relative paths, ignoring case and including dot files', () => {
11
+ const isMatch = createTestFilesMatcher('test/platform/{daily,hourly}/**');
12
+ expect(isMatch('test/platform/daily/a.test.ts')).toBe(true);
13
+ expect(isMatch('Test/Platform/Hourly/.b.test.ts')).toBe(true);
14
+ expect(isMatch('test/platform/daily-without-pr/a.test.ts')).toBe(false);
15
+ expect(isMatch('test/platform/utils.ts')).toBe(false);
16
+ });
17
+
18
+ it('matches nothing without a glob', () => {
19
+ expect(createTestFilesMatcher(undefined)('test/platform/a.test.ts')).toBe(false);
20
+ });
21
+
22
+ it('rejects an absolute glob', () => {
23
+ expect(() => createTestFilesMatcher('/repo/test/platform/**')).toThrow('must be relative to the repo root');
24
+ });
25
+ });
26
+
27
+ describe('resolveTestFilesGlob', () => {
28
+ let repoDir: string;
29
+
30
+ beforeAll(async () => {
31
+ repoDir = await mkdtemp(path.join(os.tmpdir(), 'apify-test-files-glob-'));
32
+ for (const file of [
33
+ 'test/platform/utils.ts',
34
+ 'test/platform/daily/a.test.ts',
35
+ 'test/platform/daily-without-pr/b.test.ts',
36
+ 'test/platform/hourly/nested/c.test.ts',
37
+ 'test/platform/node_modules/d.test.ts',
38
+ 'other/test/platform/daily/e.test.ts',
39
+ ]) {
40
+ await mkdir(path.join(repoDir, path.dirname(file)), { recursive: true });
41
+ await writeFile(path.join(repoDir, file), '');
42
+ }
43
+ });
44
+
45
+ afterAll(async () => {
46
+ if (repoDir) await rm(repoDir, { recursive: true, force: true });
47
+ });
48
+
49
+ const files = async (glob: string) => (await resolveTestFilesGlob(glob, repoDir)).files;
50
+ const paths = async (glob: string) => (await resolveTestFilesGlob(glob, repoDir)).paths;
51
+
52
+ it('lists the matched files from the repo root, sorted, skipping node_modules', async () => {
53
+ expect(await files('test/platform/**')).toEqual([
54
+ 'test/platform/daily-without-pr/b.test.ts',
55
+ 'test/platform/daily/a.test.ts',
56
+ 'test/platform/hourly/nested/c.test.ts',
57
+ 'test/platform/utils.ts',
58
+ ]);
59
+ });
60
+
61
+ it('selects folders with a brace pattern', async () => {
62
+ expect(await files('test/platform/{daily,hourly}/**')).toEqual([
63
+ 'test/platform/daily/a.test.ts',
64
+ 'test/platform/hourly/nested/c.test.ts',
65
+ ]);
66
+ });
67
+
68
+ it('accepts a leading ./', async () => {
69
+ expect(await files('./test/platform/daily/**')).toEqual(['test/platform/daily/a.test.ts']);
70
+ });
71
+
72
+ it('walks the whole repo when the glob has no static base', async () => {
73
+ expect(await files('**/daily/*.test.ts')).toEqual([
74
+ 'other/test/platform/daily/e.test.ts',
75
+ 'test/platform/daily/a.test.ts',
76
+ ]);
77
+ });
78
+
79
+ it('resolves a glob without wildcards to the file itself', async () => {
80
+ expect(await resolveTestFilesGlob('test/platform/utils.ts', repoDir)).toStrictEqual({
81
+ files: ['test/platform/utils.ts'],
82
+ paths: ['test/platform/utils.ts'],
83
+ });
84
+ });
85
+
86
+ it('matches no file with a glob relative to test/platform or a folder without **', async () => {
87
+ expect(await files('daily/**')).toEqual([]);
88
+ expect(await files('test/platform/daily')).toEqual([]);
89
+ });
90
+
91
+ it('gives a folder whose files all match as one path, ending with / so it is not a prefix of a sibling', async () => {
92
+ expect(await paths('test/platform/{daily,hourly}/**')).toEqual([
93
+ 'test/platform/daily/',
94
+ 'test/platform/hourly/',
95
+ ]);
96
+ });
97
+
98
+ it('lists the matched files one by one in a folder that also holds files the glob leaves out', async () => {
99
+ // test/platform/utils.ts is left out, so test/platform/ can't be one path
100
+ expect(await paths('test/platform/**/*.test.ts')).toEqual([
101
+ 'test/platform/daily-without-pr/',
102
+ 'test/platform/daily/',
103
+ 'test/platform/hourly/',
104
+ ]);
105
+ // other/ holds only the matched file, so it is the highest folder that can be one path
106
+ expect(await paths('**/daily/*.test.ts')).toEqual(['other/', 'test/platform/daily/']);
107
+ });
108
+
109
+ it('never gives a folder that holds a skipped node_modules as one path', async () => {
110
+ // test/platform/node_modules/ is not walked, so test/platform/ might hold files the glob leaves out
111
+ expect(await paths('test/platform/**')).toEqual([
112
+ 'test/platform/daily-without-pr/',
113
+ 'test/platform/daily/',
114
+ 'test/platform/hourly/',
115
+ 'test/platform/utils.ts',
116
+ ]);
117
+ });
118
+ });
@@ -4,7 +4,12 @@ import path from 'node:path';
4
4
 
5
5
  import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6
6
 
7
- import { _privates, parseConfigFile, parseResolvedConfigFile } from '../../../../../bin/utils/config/parser.js';
7
+ import {
8
+ _privates,
9
+ parseConfigFile,
10
+ parseRepoSettings,
11
+ parseResolvedConfigFile,
12
+ } from '../../../../../bin/utils/config/parser.js';
8
13
  import { CONFIG_FILE_STRATEGY, type ResolvedActorConfig } from '../../../../../bin/utils/config/structures/base.js';
9
14
  import { GROUPED_PARSER } from '../../../../../bin/utils/config/structures/grouped.js';
10
15
  import { LEGACY_PARSER } from '../../../../../bin/utils/config/structures/legacy.js';
@@ -228,3 +233,21 @@ describe('parseConfigFile', () => {
228
233
  ).toThrow('Duplicate folder "test"');
229
234
  });
230
235
  });
236
+
237
+ describe('parseRepoSettings', () => {
238
+ it('defaults platformTestsGlob to test/platform/**', () => {
239
+ expect(parseRepoSettings({ actors: [actor()] })).toStrictEqual({ platformTestsGlob: 'test/platform/**' });
240
+ });
241
+
242
+ it('reads platformTestsGlob next to the Actor entries, in any mode', () => {
243
+ expect(parseRepoSettings({ platformTestsGlob: 'e2e/**', actors: [actor()] })).toStrictEqual({
244
+ platformTestsGlob: 'e2e/**',
245
+ });
246
+ expect(parseConfigFile({ platformTestsGlob: 'e2e/**', actors: [actor({ folder: 'bin' })] })).toHaveLength(1);
247
+ });
248
+
249
+ it('rejects an empty or non-string platformTestsGlob', () => {
250
+ expect(() => parseRepoSettings({ platformTestsGlob: '' })).toThrow(/platformTestsGlob/);
251
+ expect(() => parseRepoSettings({ platformTestsGlob: ['test/**'] })).toThrow(/platformTestsGlob/);
252
+ });
253
+ });