@empiricalrun/test-run 0.1.2 → 0.2.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @empiricalrun/test-run
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 7ea52ce: fix: set default value of options.project
8
+
9
+ ## 0.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - c6c6b49: feat: support project filtering via cli options
14
+
3
15
  ## 0.1.2
4
16
 
5
17
  ### Patch Changes
package/dist/bin/index.js CHANGED
@@ -3,35 +3,40 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  const __1 = require("..");
6
+ const utils_1 = require("../utils");
6
7
  (async function main() {
7
8
  // TODO: add support for suites
8
9
  commander_1.program
9
10
  .option("-n, --name <test-name>", "Name of the test to run")
10
11
  .option("-d, --dir <test-dir>", "Path to the test directory")
12
+ .option("-p, --project <project-name...>", "Test projects to run")
13
+ .option("--forbid-only", `This options forbids the use of ".only" in the test files`)
11
14
  .allowUnknownOption();
12
15
  commander_1.program.parse(process.argv);
13
16
  // Accessing the extracted parameters
14
17
  const options = commander_1.program.opts();
15
- if (!options.name) {
16
- console.error("Please provide a test name");
18
+ if (options.name && options.forbidOnly) {
19
+ console.error("--name and --forbid-only options cannot be used together");
17
20
  process.exit(1);
18
21
  }
22
+ options.project = options.project || ["*"];
19
23
  const optionsToStrip = [
20
24
  "-n",
21
25
  "--name",
22
26
  "-d",
23
27
  "--dir",
28
+ "-p",
29
+ "--project",
24
30
  options.name,
25
31
  options.dir,
32
+ ...options.project, // an array of comma separated project names/pattern matching globs *,premium-*,super-premium-*,safari
26
33
  ];
27
34
  const pwOptions = process.argv
28
35
  .slice(2)
29
36
  .filter((arg) => !optionsToStrip.includes(arg));
30
- if (pwOptions.includes("--forbid-only")) {
31
- console.error("forbid-only option is not supported");
32
- process.exit(1);
33
- }
34
37
  try {
38
+ const projectFilters = await (0, utils_1.generateProjectFilters)(options.project);
39
+ pwOptions.push(...projectFilters);
35
40
  const { hasTestPassed } = await (0, __1.runTest)({
36
41
  name: options.name,
37
42
  dir: options.dir || "tests",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAW5C;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,EAC5B,IAAI,EACJ,GAAG,EACH,SAAS,GACV,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC,CAoDD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,EAC5B,IAAI,EACJ,GAAG,EACH,SAAS,GACV,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC,CAMD"}
package/dist/index.js CHANGED
@@ -1,67 +1,17 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.runTest = void 0;
7
- const fs_extra_1 = __importDefault(require("fs-extra"));
8
- const utils_1 = require("./utils");
4
+ const run_all_tests_1 = require("./run-all-tests");
5
+ const run_specific_test_1 = require("./run-specific-test");
9
6
  /**
10
7
  *
11
8
  * @export
12
9
  * @param {TestRunParameters} { name, dir, pwOptions }
13
10
  */
14
11
  async function runTest({ name, dir, pwOptions, }) {
15
- const files = await (0, utils_1.getAllFilePaths)(dir);
16
- let matchingFilePath = "";
17
- // find the first file that contains the test block
18
- // TODO: add suites support
19
- for (const file of files) {
20
- const match = await (0, utils_1.hasTestBlock)({ filePath: file, scenarioName: name });
21
- if (match) {
22
- matchingFilePath = file;
23
- break;
24
- }
12
+ if (name) {
13
+ return await (0, run_specific_test_1.runSpecificTest)({ name, dir, pwOptions });
25
14
  }
26
- if (!matchingFilePath) {
27
- const message = `No test block found for the given test name: ${name}`;
28
- throw Error(message);
29
- }
30
- const { testCaseNode, sourceFile } = await (0, utils_1.getTestCaseNode)({
31
- filePath: matchingFilePath,
32
- scenarioName: name,
33
- });
34
- const parentDescribe = (0, utils_1.findFirstSerialDescribeBlock)(testCaseNode);
35
- const isFileMarkedSerial = await (0, utils_1.hasTopLevelDescribeConfigureWithSerialMode)(matchingFilePath);
36
- console.log("Identified test block:", !!testCaseNode);
37
- console.log("Is parent describe block marked serial:", !!parentDescribe);
38
- console.log("Is file marked serial:", isFileMarkedSerial);
39
- const currentFileContent = await fs_extra_1.default.readFile(matchingFilePath, "utf-8");
40
- // if the file is not marked serial, we need to mark the test or describe block as only
41
- if (!isFileMarkedSerial && testCaseNode) {
42
- await (0, utils_1.markTestAsOnly)({
43
- sourceFile,
44
- parentDescribeNode: parentDescribe,
45
- testCaseNode: testCaseNode,
46
- filePath: matchingFilePath,
47
- });
48
- }
49
- let hasTestPassed = true;
50
- try {
51
- const env = Object({ ...process.env });
52
- const pwRunCmd = `npx playwright test ${matchingFilePath} ${pwOptions}`;
53
- console.log("Playwright test command:", pwRunCmd);
54
- await (0, utils_1.cmd)(pwRunCmd.split(" "), {
55
- env,
56
- });
57
- }
58
- catch (e) {
59
- hasTestPassed = false;
60
- }
61
- // revert the changes made to the file to mark tests as only
62
- await fs_extra_1.default.writeFile(matchingFilePath, currentFileContent);
63
- return {
64
- hasTestPassed,
65
- };
15
+ return await (0, run_all_tests_1.runAllTests)({ pwOptions });
66
16
  }
67
17
  exports.runTest = runTest;
@@ -0,0 +1,11 @@
1
+ /**
2
+ *
3
+ * @export
4
+ * @param {TestRunParameters} { name, dir, pwOptions }
5
+ */
6
+ export declare function runAllTests({ pwOptions, }: {
7
+ pwOptions?: string;
8
+ }): Promise<{
9
+ hasTestPassed: boolean;
10
+ }>;
11
+ //# sourceMappingURL=run-all-tests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-all-tests.d.ts","sourceRoot":"","sources":["../src/run-all-tests.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,EAChC,SAAS,GACV,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC;IACV,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC,CAkBD"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAllTests = void 0;
4
+ const utils_1 = require("./utils");
5
+ /**
6
+ *
7
+ * @export
8
+ * @param {TestRunParameters} { name, dir, pwOptions }
9
+ */
10
+ async function runAllTests({ pwOptions, }) {
11
+ let hasTestPassed = true;
12
+ try {
13
+ const env = Object({ ...process.env });
14
+ const pwRunCmd = `npx playwright test ${pwOptions}`;
15
+ console.log("Playwright test command:", pwRunCmd, {
16
+ arr: pwRunCmd.split(" "),
17
+ });
18
+ await (0, utils_1.cmd)(pwRunCmd.split(" "), {
19
+ env,
20
+ });
21
+ }
22
+ catch (e) {
23
+ hasTestPassed = false;
24
+ }
25
+ return {
26
+ hasTestPassed,
27
+ };
28
+ }
29
+ exports.runAllTests = runAllTests;
@@ -0,0 +1,10 @@
1
+ import { TestRunParameters } from "./types";
2
+ /**
3
+ *
4
+ * @export
5
+ * @param {TestRunParameters} { name, dir, pwOptions }
6
+ */
7
+ export declare function runSpecificTest({ name, dir, pwOptions, }: TestRunParameters): Promise<{
8
+ hasTestPassed: boolean;
9
+ }>;
10
+ //# sourceMappingURL=run-specific-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-specific-test.d.ts","sourceRoot":"","sources":["../src/run-specific-test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAW5C;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,EACpC,IAAI,EACJ,GAAG,EACH,SAAS,GACV,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC,CAoDD"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runSpecificTest = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const utils_1 = require("./utils");
9
+ /**
10
+ *
11
+ * @export
12
+ * @param {TestRunParameters} { name, dir, pwOptions }
13
+ */
14
+ async function runSpecificTest({ name, dir, pwOptions, }) {
15
+ const files = await (0, utils_1.getAllFilePaths)(dir);
16
+ let matchingFilePath = "";
17
+ // find the first file that contains the test block
18
+ // TODO: add suites support
19
+ for (const file of files) {
20
+ const match = await (0, utils_1.hasTestBlock)({ filePath: file, scenarioName: name });
21
+ if (match) {
22
+ matchingFilePath = file;
23
+ break;
24
+ }
25
+ }
26
+ if (!matchingFilePath) {
27
+ const message = `No test block found for the given test name: ${name}`;
28
+ throw Error(message);
29
+ }
30
+ const { testCaseNode, sourceFile } = await (0, utils_1.getTestCaseNode)({
31
+ filePath: matchingFilePath,
32
+ scenarioName: name,
33
+ });
34
+ const parentDescribe = (0, utils_1.findFirstSerialDescribeBlock)(testCaseNode);
35
+ const isFileMarkedSerial = await (0, utils_1.hasTopLevelDescribeConfigureWithSerialMode)(matchingFilePath);
36
+ console.log("Identified test block:", !!testCaseNode);
37
+ console.log("Is parent describe block marked serial:", !!parentDescribe);
38
+ console.log("Is file marked serial:", isFileMarkedSerial);
39
+ const currentFileContent = await fs_extra_1.default.readFile(matchingFilePath, "utf-8");
40
+ // if the file is not marked serial, we need to mark the test or describe block as only
41
+ if (!isFileMarkedSerial && testCaseNode) {
42
+ await (0, utils_1.markTestAsOnly)({
43
+ sourceFile,
44
+ parentDescribeNode: parentDescribe,
45
+ testCaseNode: testCaseNode,
46
+ filePath: matchingFilePath,
47
+ });
48
+ }
49
+ let hasTestPassed = true;
50
+ try {
51
+ const env = Object({ ...process.env });
52
+ const pwRunCmd = `npx playwright test ${matchingFilePath} ${pwOptions}`;
53
+ console.log("Playwright test command:", pwRunCmd);
54
+ await (0, utils_1.cmd)(pwRunCmd.split(" "), {
55
+ env,
56
+ });
57
+ }
58
+ catch (e) {
59
+ hasTestPassed = false;
60
+ }
61
+ // revert the changes made to the file to mark tests as only
62
+ await fs_extra_1.default.writeFile(matchingFilePath, currentFileContent);
63
+ return {
64
+ hasTestPassed,
65
+ };
66
+ }
67
+ exports.runSpecificTest = runSpecificTest;
@@ -28,4 +28,7 @@ export declare function markTestAsOnly({ sourceFile, parentDescribeNode, testCas
28
28
  testCaseNode: Node;
29
29
  filePath: string;
30
30
  }): Promise<void>;
31
+ export declare function getProjectsFromPlaywrightConfig(): Promise<string[]>;
32
+ export declare const filterArrayByGlobMatchersSet: (input: string[], globMatcherSets: string[][]) => string[];
33
+ export declare const generateProjectFilters: (filteringSets: string[]) => Promise<string[]>;
31
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAW,UAAU,EAAc,MAAM,UAAU,CAAC;AAEjE,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,GAAE,MAAW,GACzB,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB;AAED,wBAAsB,eAAe,CAAC,EACpC,QAAQ,EACR,YAAY,GACb,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,YAAY,EAAE,IAAI,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,CAatE;AAED,wBAAsB,YAAY,CAAC,EACjC,QAAQ,EACR,YAAY,GACb,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,GAAG,SAAS,CA2BlB;AAED,wBAAsB,0CAA0C,CAC9D,QAAQ,EAAE,MAAM,oBA+BjB;AAED,wBAAsB,cAAc,CAAC,EACnC,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACtC,YAAY,EAAE,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBAgBA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAW,UAAU,EAAc,MAAM,UAAU,CAAC;AAGjE,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,GAAE,MAAW,GACzB,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB;AAED,wBAAsB,eAAe,CAAC,EACpC,QAAQ,EACR,YAAY,GACb,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,YAAY,EAAE,IAAI,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,CAatE;AAED,wBAAsB,YAAY,CAAC,EACjC,QAAQ,EACR,YAAY,GACb,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,GAAG,SAAS,CA2BlB;AAED,wBAAsB,0CAA0C,CAC9D,QAAQ,EAAE,MAAM,oBA+BjB;AAED,wBAAsB,cAAc,CAAC,EACnC,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACtC,YAAY,EAAE,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBAgBA;AAED,wBAAsB,+BAA+B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAczE;AAED,eAAO,MAAM,4BAA4B,UAEhC,MAAM,EAAE,mBAGE,MAAM,EAAE,EAAE,KAC1B,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,sBAAsB,kBAClB,MAAM,EAAE,KACtB,QAAQ,MAAM,EAAE,CAkBlB,CAAC"}
@@ -3,11 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.markTestAsOnly = exports.hasTopLevelDescribeConfigureWithSerialMode = exports.findFirstSerialDescribeBlock = exports.hasTestBlock = exports.getTestCaseNode = exports.getAllFilePaths = exports.cmd = void 0;
6
+ exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.getProjectsFromPlaywrightConfig = exports.markTestAsOnly = exports.hasTopLevelDescribeConfigureWithSerialMode = exports.findFirstSerialDescribeBlock = exports.hasTestBlock = exports.getTestCaseNode = exports.getAllFilePaths = exports.cmd = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const minimatch_1 = require("minimatch");
9
10
  const path_1 = __importDefault(require("path"));
10
11
  const ts_morph_1 = require("ts-morph");
12
+ const api_1 = require("tsx/esm/api");
11
13
  function cmd(command, options) {
12
14
  let errorLogs = [];
13
15
  return new Promise((resolveFunc, rejectFunc) => {
@@ -157,3 +159,41 @@ async function markTestAsOnly({ sourceFile, parentDescribeNode, testCaseNode, fi
157
159
  await fs_extra_1.default.writeFile(filePath, updatedTestFileContent, "utf-8");
158
160
  }
159
161
  exports.markTestAsOnly = markTestAsOnly;
162
+ async function getProjectsFromPlaywrightConfig() {
163
+ const configName = "playwright.config.ts";
164
+ const directoryPath = ".";
165
+ const pwFile = path_1.default.resolve(directoryPath, configName);
166
+ try {
167
+ const projectConfig = await (0, api_1.tsImport)(pwFile, __filename);
168
+ return projectConfig?.default?.default?.projects?.map((projectConfig) => projectConfig.name);
169
+ }
170
+ catch (err) {
171
+ console.error("Error getting project list from playwright config", err);
172
+ }
173
+ return [];
174
+ }
175
+ exports.getProjectsFromPlaywrightConfig = getProjectsFromPlaywrightConfig;
176
+ const filterArrayByGlobMatchersSet = (
177
+ // array that needs to be filtered
178
+ input,
179
+ // set of glob patterns to filter the array sequentially (takes the intersection of all sets)
180
+ globMatcherSets) => {
181
+ let filteredList = input;
182
+ globMatcherSets.forEach((matcherSet) => {
183
+ filteredList = filteredList.filter((item) => {
184
+ return matcherSet.some((matcherGlob) => (0, minimatch_1.minimatch)(item, matcherGlob));
185
+ });
186
+ });
187
+ return filteredList;
188
+ };
189
+ exports.filterArrayByGlobMatchersSet = filterArrayByGlobMatchersSet;
190
+ const generateProjectFilters = async (filteringSets) => {
191
+ const projectsDefinedInPwConfig = await getProjectsFromPlaywrightConfig();
192
+ const filters = filteringSets.map((matchingString) => matchingString.split(","));
193
+ const filteredProjects = (0, exports.filterArrayByGlobMatchersSet)(projectsDefinedInPwConfig, filters);
194
+ if (filteredProjects.length === 0) {
195
+ throw new Error("No projects found in playwright config that matches the filtering criteria");
196
+ }
197
+ return filteredProjects.map((projectName) => `--project ${projectName}`);
198
+ };
199
+ exports.generateProjectFilters = generateProjectFilters;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-run",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -17,7 +17,9 @@
17
17
  "dependencies": {
18
18
  "commander": "^12.1.0",
19
19
  "fs-extra": "^11.2.0",
20
- "ts-morph": "^23.0.0"
20
+ "minimatch": "^10.0.1",
21
+ "ts-morph": "^23.0.0",
22
+ "tsx": "^4.16.2"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@types/fs-extra": "^11.0.4",