@itmetsam/samurai-framework 0.15.2 → 0.17.0

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 (60) hide show
  1. package/dist/browser/browser.js +2 -1
  2. package/dist/browser/page-network.js +3 -2
  3. package/dist/browser/page-settings.js +2 -5
  4. package/dist/browser/page.js +2 -1
  5. package/dist/browser/session.d.ts +1 -1
  6. package/dist/browser/session.js +1 -1
  7. package/dist/browser/viewport.d.ts +0 -5
  8. package/dist/browser/viewport.js +0 -2
  9. package/dist/capabilities.d.ts +36 -0
  10. package/dist/capabilities.js +34 -0
  11. package/dist/{runner/browsers.js → config/browsers-for.js} +1 -2
  12. package/dist/config/config.d.ts +5 -1
  13. package/dist/config/config.js +18 -12
  14. package/dist/config/defaults.d.ts +41 -0
  15. package/dist/config/defaults.js +38 -0
  16. package/dist/config/resolved-config.d.ts +134 -0
  17. package/dist/config/resolved-config.js +135 -0
  18. package/dist/config/run-settings.d.ts +2 -0
  19. package/dist/config/run-settings.js +2 -3
  20. package/dist/config/timeouts.js +1 -2
  21. package/dist/{runner → config}/workers-option.js +1 -1
  22. package/dist/flows/run/project-options.d.ts +2 -0
  23. package/dist/flows/run/project.js +11 -4
  24. package/dist/network/data-collector.js +1 -2
  25. package/dist/network/router.js +1 -2
  26. package/dist/project/files-store.js +2 -1
  27. package/dist/project/index.d.ts +1 -0
  28. package/dist/project/index.js +1 -0
  29. package/dist/recorder/session-browser.js +1 -1
  30. package/dist/recorder/session.d.ts +5 -2
  31. package/dist/recorder/session.js +18 -3
  32. package/dist/runner/groups.d.ts +4 -5
  33. package/dist/runner/groups.js +2 -5
  34. package/dist/runner/options.d.ts +7 -1
  35. package/dist/runner/output.d.ts +6 -0
  36. package/dist/runner/output.js +14 -0
  37. package/dist/runner/page-logs-report.js +6 -3
  38. package/dist/runner/project.js +2 -2
  39. package/dist/runner/retries-option.js +2 -1
  40. package/dist/runner/run-test.js +2 -5
  41. package/dist/runner/run.d.ts +12 -2
  42. package/dist/runner/run.js +21 -7
  43. package/dist/runner/select.js +4 -4
  44. package/dist/runner/test-dir.js +2 -1
  45. package/dist/runner/test-runner.d.ts +8 -2
  46. package/dist/runner/test-runner.js +16 -10
  47. package/dist/runner/worker-protocol.d.ts +2 -0
  48. package/dist/runner/worker-session.js +3 -0
  49. package/dist/steps/edit.d.ts +10 -0
  50. package/dist/steps/edit.js +4 -0
  51. package/dist/steps/index.d.ts +2 -2
  52. package/dist/steps/index.js +1 -1
  53. package/dist/steps/model.d.ts +17 -0
  54. package/dist/steps/parse.d.ts +6 -1
  55. package/dist/steps/parse.js +54 -10
  56. package/dist/transport/bidi-connection.d.ts +0 -2
  57. package/dist/transport/bidi-connection.js +1 -2
  58. package/package.json +6 -2
  59. /package/dist/{runner/browsers.d.ts → config/browsers-for.d.ts} +0 -0
  60. /package/dist/{runner → config}/workers-option.d.ts +0 -0
@@ -1,6 +1,6 @@
1
- export { parseSpec } from "./parse.js";
1
+ export { parseSpec, parseSpecFile } from "./parse.js";
2
2
  export { applyEdit, type StepEdit } from "./edit.js";
3
3
  export { stepToSource, locatorToSource, callToSource, filesToSource, valueToSource, } from "./emit.js";
4
4
  export { formatSize, humanSize, parseSize, sizeFromSource } from "./files.js";
5
5
  export { locatorFromSpec } from "./build.js";
6
- export type { Expectation, FileEntry, GeneratedFileSpec, LocatorCall, LocatorSpec, ParsedStep, ParsedTest, Step, TextMatcher, TextValue, } from "./model.js";
6
+ export type { Expectation, FileEntry, GeneratedFileSpec, LocatorCall, LocatorSpec, ParsedStep, ParsedTest, Step, TextMatcher, TextValue, UnparsedTest, } from "./model.js";
@@ -1,4 +1,4 @@
1
- export { parseSpec } from "./parse.js";
1
+ export { parseSpec, parseSpecFile } from "./parse.js";
2
2
  export { applyEdit } from "./edit.js";
3
3
  export { stepToSource, locatorToSource, callToSource, filesToSource, valueToSource, } from "./emit.js";
4
4
  export { formatSize, humanSize, parseSize, sizeFromSource } from "./files.js";
@@ -146,6 +146,15 @@ export interface ParsedTest {
146
146
  /** Offset just after the `{` of the test body, and of its closing `}` */
147
147
  bodyStart: number;
148
148
  bodyEnd: number;
149
+ /** Where the whole test starts: the full start of its statement, so the comments and blank line above are included */
150
+ start: number;
151
+ /** Where the whole test ends: the end of its statement, after the `;` */
152
+ end: number;
153
+ /**
154
+ * False when the test call isn't a statement of its own (a loop or `if` body without braces, an arrow's
155
+ * expression body), so it can't be removed without breaking the file
156
+ */
157
+ removable: boolean;
149
158
  /**
150
159
  * The fixtures the test's callback destructures, and the offset where another one goes: after the last one
151
160
  * (`list`), inside an empty `{}` (`empty`) or inside the empty `()` of a callback without parameters (`none`).
@@ -161,3 +170,11 @@ export interface ParsedTest {
161
170
  /** The `{ reason }` written with the `skip` or `fixme` that applies, when it is a string literal */
162
171
  reason?: string;
163
172
  }
173
+ /** A `test` or `describe` call the codec can't read: its title isn't a plain string, or its function isn't written inline */
174
+ export interface UnparsedTest {
175
+ kind: "test" | "describe";
176
+ /** The statement's range, as for `ParsedTest` */
177
+ start: number;
178
+ end: number;
179
+ reason: "dynamic-title" | "not-inline";
180
+ }
@@ -1,3 +1,8 @@
1
- import type { ParsedTest } from "./model.js";
1
+ import type { ParsedTest, UnparsedTest } from "./model.js";
2
+ /** The tests in a spec file, with their steps and where each statement sits, and the tests and describes it can't read */
3
+ export declare function parseSpecFile(source: string): {
4
+ tests: ParsedTest[];
5
+ unparsed: UnparsedTest[];
6
+ };
2
7
  /** The tests in a spec file, with their steps and where each statement sits */
3
8
  export declare function parseSpec(source: string): ParsedTest[];
@@ -85,24 +85,64 @@ function effectiveMark(own, describes) {
85
85
  };
86
86
  return chain.some((m) => m.modifier === "only") ? { modifier: "only" } : {};
87
87
  }
88
- /** The tests in a spec file, with their steps and where each statement sits */
89
- export function parseSpec(source) {
88
+ /** The range of a whole test: its statement with the comments above it, or the call alone when it isn't a statement */
89
+ function wholeRange(call) {
90
+ const statement = ts.isExpressionStatement(call.parent) ? call.parent : call;
91
+ return { start: statement.getFullStart(), end: statement.end };
92
+ }
93
+ /** Whether the call is a statement directly inside a block, a module block or the file, so removing its range leaves valid code */
94
+ function isRemovable(call) {
95
+ const statement = call.parent;
96
+ if (!ts.isExpressionStatement(statement))
97
+ return false;
98
+ const container = statement.parent;
99
+ return (ts.isBlock(container) ||
100
+ ts.isSourceFile(container) ||
101
+ ts.isModuleBlock(container));
102
+ }
103
+ /** A test or describe the codec can't read, with where its statement sits */
104
+ function unreadable(node, kind, reason) {
105
+ return { kind, reason, ...wholeRange(node) };
106
+ }
107
+ /** `test.skip(condition, reason)` inside a body: it takes a condition where a test takes a title, so it is neither a test nor unparsed */
108
+ function isConditionForm(node, callee) {
109
+ const [first] = node.arguments;
110
+ return ((callee.modifier === "skip" || callee.modifier === "fixme") &&
111
+ !(first &&
112
+ (ts.isStringLiteral(first) ||
113
+ ts.isNoSubstitutionTemplateLiteral(first) ||
114
+ ts.isTemplateExpression(first))));
115
+ }
116
+ /** The tests in a spec file, with their steps and where each statement sits, and the tests and describes it can't read */
117
+ export function parseSpecFile(source) {
90
118
  const sourceFile = ts.createSourceFile("spec.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
91
119
  const tests = [];
120
+ const unparsed = [];
92
121
  const visit = (node, titles, describes) => {
93
122
  const callee = ts.isCallExpression(node)
94
123
  ? calleeOf(node.expression)
95
124
  : undefined;
96
- if (ts.isCallExpression(node) && callee) {
125
+ if (ts.isCallExpression(node) &&
126
+ callee &&
127
+ (callee.name === "test" || callee.name === "describe") &&
128
+ !(callee.name === "test" && isConditionForm(node, callee))) {
129
+ const kind = callee.name;
97
130
  const title = stringOf(node.arguments[0]);
98
131
  const { callback, reason } = callbackOf(node, callee.modifier);
99
132
  const body = callbackBody(callback);
133
+ if (title === undefined) {
134
+ unparsed.push(unreadable(node, kind, "dynamic-title"));
135
+ return;
136
+ }
137
+ if (!body) {
138
+ unparsed.push(unreadable(node, kind, "not-inline"));
139
+ return;
140
+ }
100
141
  const mark = {
101
142
  ...(callee.modifier && { modifier: callee.modifier }),
102
143
  ...(reason !== undefined && { reason }),
103
144
  };
104
- if (title !== undefined && body && callee.name === "test") {
105
- // `test.skip(condition, reason)` has no title and no body, so it stays a step of the test it is in
145
+ if (kind === "test") {
106
146
  const titlePath = [...titles, title];
107
147
  tests.push({
108
148
  titlePath,
@@ -110,18 +150,22 @@ export function parseSpec(source) {
110
150
  steps: body.statements.map((statement) => parseStatement(source, statement, sourceFile)),
111
151
  bodyStart: body.getStart(sourceFile) + 1,
112
152
  bodyEnd: body.end - 1,
153
+ ...wholeRange(node),
154
+ removable: isRemovable(node),
113
155
  fixtures: callbackFixtures(callback, sourceFile),
114
156
  ...effectiveMark(mark, describes),
115
157
  });
116
158
  return;
117
159
  }
118
- if (title !== undefined && body && callee.name === "describe") {
119
- ts.forEachChild(body, (child) => visit(child, [...titles, title], [...describes, mark]));
120
- return;
121
- }
160
+ ts.forEachChild(body, (child) => visit(child, [...titles, title], [...describes, mark]));
161
+ return;
122
162
  }
123
163
  ts.forEachChild(node, (child) => visit(child, titles, describes));
124
164
  };
125
165
  visit(sourceFile, [], []);
126
- return tests;
166
+ return { tests, unparsed };
167
+ }
168
+ /** The tests in a spec file, with their steps and where each statement sits */
169
+ export function parseSpec(source) {
170
+ return parseSpecFile(source).tests;
127
171
  }
@@ -1,7 +1,5 @@
1
1
  import type { BiDiCommands, BiDiEvents } from "../types/bidi.js";
2
2
  import { type EventName, type Subscription, type SubscribeOptions } from "./subscriptions.js";
3
- /** Time (ms) a command may wait for its reply before it fails with code "timeout" */
4
- export declare const DEFAULT_COMMAND_TIMEOUT = 30000;
5
3
  export interface ConnectorOptions {
6
4
  /** @default DEFAULT_COMMAND_TIMEOUT */
7
5
  commandTimeout?: number;
@@ -1,11 +1,10 @@
1
1
  import EventEmitter from "node:events";
2
+ import { DEFAULT_COMMAND_TIMEOUT } from "../config/defaults.js";
2
3
  import logger from "../logger/index.js";
3
4
  import { WaitTimeoutError } from "../wait/wait-until.js";
4
5
  import { BiDiError } from "./bidi-error.js";
5
6
  import { forLog } from "./log-redaction.js";
6
7
  import { SubscriptionManager, } from "./subscriptions.js";
7
- /** Time (ms) a command may wait for its reply before it fails with code "timeout" */
8
- export const DEFAULT_COMMAND_TIMEOUT = 30000;
9
8
  /**
10
9
  * The connection to the browser is gone (the browser exited or was closed, or the socket dropped). Commands that
11
10
  * were waiting fail with it, and so does every command sent afterwards: a closed socket would swallow them silently.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itmetsam/samurai-framework",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
4
4
  "description": "Browser test framework over WebDriver BiDi: auto-waiting locators, environments and secrets, a recorder, and a command line.",
5
5
  "main": "dist/api.js",
6
6
  "exports": {
@@ -48,6 +48,10 @@
48
48
  "types": "./dist/project/index.d.ts",
49
49
  "default": "./dist/project/index.js"
50
50
  },
51
+ "./capabilities": {
52
+ "types": "./dist/capabilities.d.ts",
53
+ "default": "./dist/capabilities.js"
54
+ },
51
55
  "./package.json": "./package.json"
52
56
  },
53
57
  "scripts": {
@@ -55,7 +59,7 @@
55
59
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/copy-types.mjs",
56
60
  "prepack": "npm run build",
57
61
  "test": "SAMURAI_LOG_LEVEL=${SAMURAI_LOG_LEVEL:-warn} tsx --import ./scripts/test-tmp.mjs --test --test-timeout=10000 \"src/**/*.test.ts\"",
58
- "test:browser": "SAMURAI_LOG_LEVEL=${SAMURAI_LOG_LEVEL:-warn} tsx --import ./scripts/test-tmp.mjs --test --test-concurrency=1 --test-timeout=60000 \"src/**/*.browser-test.ts\"",
62
+ "test:browser": "SAMURAI_LOG_LEVEL=${SAMURAI_LOG_LEVEL:-warn} tsx --import ./scripts/test-tmp.mjs --test --test-concurrency=1 --test-timeout=60000 ${SAMURAI_TEST_SHARD:+--test-shard=$SAMURAI_TEST_SHARD} \"src/**/*.browser-test.ts\"",
59
63
  "test:browser:chrome": "SAMURAI_TEST_BROWSER=chrome npm run test:browser",
60
64
  "test:cli": "tsx --import ./scripts/test-tmp.mjs --test --test-concurrency=1 \"src/**/*.cli-test.ts\""
61
65
  },
File without changes