flowstride 1.0.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/dist/adapters/api/undici.d.ts +46 -0
  4. package/dist/adapters/api/undici.js +118 -0
  5. package/dist/adapters/web/playwright.d.ts +51 -0
  6. package/dist/adapters/web/playwright.js +1108 -0
  7. package/dist/cli/commands/init.d.ts +1 -0
  8. package/dist/cli/commands/init.js +77 -0
  9. package/dist/cli/commands/report.d.ts +1 -0
  10. package/dist/cli/commands/report.js +78 -0
  11. package/dist/cli/commands/run.d.ts +1 -0
  12. package/dist/cli/commands/run.js +158 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +39 -0
  15. package/dist/parser/lexer.d.ts +29 -0
  16. package/dist/parser/lexer.js +425 -0
  17. package/dist/parser/parser.d.ts +41 -0
  18. package/dist/parser/parser.js +573 -0
  19. package/dist/parser/resolver.d.ts +5 -0
  20. package/dist/parser/resolver.js +19 -0
  21. package/dist/parser/validator.d.ts +4 -0
  22. package/dist/parser/validator.js +37 -0
  23. package/dist/parser/variable_resolver.d.ts +7 -0
  24. package/dist/parser/variable_resolver.js +33 -0
  25. package/dist/runner/data_generators.d.ts +55 -0
  26. package/dist/runner/data_generators.js +233 -0
  27. package/dist/runner/dom_stripper.d.ts +4 -0
  28. package/dist/runner/dom_stripper.js +61 -0
  29. package/dist/runner/events.d.ts +15 -0
  30. package/dist/runner/events.js +20 -0
  31. package/dist/runner/flow_orchestrator.d.ts +15 -0
  32. package/dist/runner/flow_orchestrator.js +216 -0
  33. package/dist/runner/flow_worker.d.ts +53 -0
  34. package/dist/runner/flow_worker.js +1112 -0
  35. package/dist/runner/persistent_vault.d.ts +12 -0
  36. package/dist/runner/persistent_vault.js +84 -0
  37. package/dist/runner/plugin_manager.d.ts +13 -0
  38. package/dist/runner/plugin_manager.js +164 -0
  39. package/dist/runner/temporary_store.d.ts +8 -0
  40. package/dist/runner/temporary_store.js +22 -0
  41. package/dist/runner/worker_entry.d.ts +1 -0
  42. package/dist/runner/worker_entry.js +63 -0
  43. package/dist/sdk/api_proxy.d.ts +8 -0
  44. package/dist/sdk/api_proxy.js +28 -0
  45. package/dist/sdk/db_proxy.d.ts +3 -0
  46. package/dist/sdk/db_proxy.js +15 -0
  47. package/dist/sessions/manager.d.ts +17 -0
  48. package/dist/sessions/manager.js +74 -0
  49. package/dist/sessions/types.d.ts +39 -0
  50. package/dist/sessions/types.js +2 -0
  51. package/dist/sessions/vault.d.ts +8 -0
  52. package/dist/sessions/vault.js +69 -0
  53. package/dist/test_data/commerce.json +341 -0
  54. package/dist/test_data/geography.json +1298 -0
  55. package/dist/test_data/identities.json +1420 -0
  56. package/dist/test_data/phone_data.json +27 -0
  57. package/dist/test_data/utility.json +246 -0
  58. package/dist/types/index.d.ts +104 -0
  59. package/dist/types/index.js +2 -0
  60. package/dist/utils/redactor.d.ts +5 -0
  61. package/dist/utils/redactor.js +53 -0
  62. package/package.json +54 -0
@@ -0,0 +1,425 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Lexer = exports.TokenType = void 0;
4
+ var TokenType;
5
+ (function (TokenType) {
6
+ TokenType["FEATURE"] = "FEATURE";
7
+ TokenType["SCENARIO"] = "SCENARIO";
8
+ TokenType["BLOCK"] = "BLOCK";
9
+ TokenType["COMMAND"] = "COMMAND";
10
+ TokenType["MODIFIER"] = "MODIFIER";
11
+ TokenType["SPATIAL"] = "SPATIAL";
12
+ TokenType["PREPOSITION"] = "PREPOSITION";
13
+ TokenType["ELEMENT"] = "ELEMENT";
14
+ TokenType["STRING"] = "STRING";
15
+ TokenType["LET"] = "LET";
16
+ TokenType["ASSIGN"] = "ASSIGN";
17
+ TokenType["ALIAS_COMMAND"] = "ALIAS_COMMAND";
18
+ TokenType["JSON_PAYLOAD"] = "JSON_PAYLOAD";
19
+ TokenType["IDENTIFIER"] = "IDENTIFIER";
20
+ TokenType["EOF"] = "EOF";
21
+ })(TokenType || (exports.TokenType = TokenType = {}));
22
+ class Lexer {
23
+ position = 0;
24
+ line = 1;
25
+ input;
26
+ constructor(rawInput) {
27
+ this.input = rawInput.replace(/[‘’]/g, "'").replace(/[“”]/g, '"');
28
+ }
29
+ tokenise() {
30
+ const tokens = [];
31
+ let expectingNakedCommand = false;
32
+ while (this.position < this.input.length) {
33
+ const char = this.input[this.position];
34
+ if (char === "\n") {
35
+ this.line++;
36
+ this.position++;
37
+ continue;
38
+ }
39
+ if (char === " " ||
40
+ char === "\t" ||
41
+ char === "\r" ||
42
+ char === ";" ||
43
+ char === ",") {
44
+ this.position++;
45
+ continue;
46
+ }
47
+ if (char === "=") {
48
+ tokens.push({ type: TokenType.ASSIGN, value: "=", line: this.line });
49
+ this.position++;
50
+ continue;
51
+ }
52
+ if (char === "{") {
53
+ let jsonStr = "";
54
+ let depth = 0;
55
+ let startLine = this.line;
56
+ while (this.position < this.input.length) {
57
+ const c = this.input[this.position];
58
+ if (c === "{")
59
+ depth++;
60
+ if (c === "}")
61
+ depth--;
62
+ if (c === "\n")
63
+ this.line++;
64
+ jsonStr += c;
65
+ this.position++;
66
+ if (depth === 0)
67
+ break;
68
+ }
69
+ if (depth > 0) {
70
+ throw new SyntaxError(`Lexer Error on line ${startLine}: Unclosed JSON payload bracket '{'`);
71
+ }
72
+ tokens.push({
73
+ type: TokenType.JSON_PAYLOAD,
74
+ value: jsonStr,
75
+ line: startLine,
76
+ });
77
+ continue;
78
+ }
79
+ if (char === '"') {
80
+ expectingNakedCommand = false;
81
+ if (this.position + 2 < this.input.length &&
82
+ this.input[this.position + 1] === '"' &&
83
+ this.input[this.position + 2] === '"') {
84
+ this.position += 3;
85
+ let str = "";
86
+ while (this.position < this.input.length) {
87
+ if (this.position + 2 < this.input.length &&
88
+ this.input[this.position] === '"' &&
89
+ this.input[this.position + 1] === '"' &&
90
+ this.input[this.position + 2] === '"') {
91
+ this.position += 3;
92
+ break;
93
+ }
94
+ if (this.input[this.position] === "\n") {
95
+ this.line++;
96
+ }
97
+ str += this.input[this.position];
98
+ this.position++;
99
+ }
100
+ tokens.push({
101
+ type: TokenType.STRING,
102
+ value: str.trim(),
103
+ line: this.line,
104
+ });
105
+ continue;
106
+ }
107
+ else {
108
+ let str = "";
109
+ this.position++;
110
+ while (this.position < this.input.length &&
111
+ this.input[this.position] !== '"') {
112
+ str += this.input[this.position];
113
+ this.position++;
114
+ }
115
+ this.position++;
116
+ tokens.push({ type: TokenType.STRING, value: str, line: this.line });
117
+ continue;
118
+ }
119
+ }
120
+ if (/[a-zA-Z_]/.test(char)) {
121
+ let ident = "";
122
+ while (this.position < this.input.length &&
123
+ /[a-zA-Z0-9_\-\.]/.test(this.input[this.position])) {
124
+ ident += this.input[this.position];
125
+ this.position++;
126
+ }
127
+ const lowerIdent = ident.toLowerCase();
128
+ if (lowerIdent === "let") {
129
+ tokens.push({ type: TokenType.LET, value: ident, line: this.line });
130
+ continue;
131
+ }
132
+ if (lowerIdent === "feature" || lowerIdent === "scenario") {
133
+ let tempPos = this.position;
134
+ while (tempPos < this.input.length &&
135
+ (this.input[tempPos] === " " || this.input[tempPos] === "\t"))
136
+ tempPos++;
137
+ if (this.input[tempPos] === '"') {
138
+ throw new SyntaxError(`Syntax Error on line ${this.line}: '${ident}' declarations must not use quotation marks.`);
139
+ }
140
+ if (this.input[tempPos] !== ":") {
141
+ throw new SyntaxError(`Syntax Error on line ${this.line}: '${ident}' declaration is missing a colon (:).`);
142
+ }
143
+ this.position = tempPos + 1;
144
+ tokens.push({
145
+ type: lowerIdent === "feature" ? TokenType.FEATURE : TokenType.SCENARIO,
146
+ value: ident,
147
+ line: this.line,
148
+ });
149
+ let description = "";
150
+ while (this.position < this.input.length &&
151
+ (this.input[this.position] === " " ||
152
+ this.input[this.position] === "\t")) {
153
+ this.position++;
154
+ }
155
+ while (this.position < this.input.length &&
156
+ this.input[this.position] !== "\n" &&
157
+ this.input[this.position] !== "\r") {
158
+ description += this.input[this.position];
159
+ this.position++;
160
+ }
161
+ if (!description.trim()) {
162
+ throw new SyntaxError(`Syntax Error on line ${this.line}: '${ident}' declaration must have a description.`);
163
+ }
164
+ tokens.push({
165
+ type: TokenType.STRING,
166
+ value: description.trim(),
167
+ line: this.line,
168
+ });
169
+ continue;
170
+ }
171
+ if (expectingNakedCommand) {
172
+ expectingNakedCommand = false;
173
+ if (lowerIdent.startsWith("flow.")) {
174
+ const correctWord = lowerIdent.replace("flow.", "");
175
+ throw new SyntaxError(`Syntax Error on line ${this.line}: Do not repeat 'flow.' after 'flow.try'. Use '${correctWord}' instead.`);
176
+ }
177
+ const validNakedCommands = [
178
+ "open",
179
+ "type",
180
+ "click",
181
+ "expect",
182
+ "use",
183
+ "save",
184
+ "upload",
185
+ "set",
186
+ "select",
187
+ "drag",
188
+ "switchTo",
189
+ "close",
190
+ "acceptDialog",
191
+ "rejectDialog",
192
+ "injectAudio",
193
+ "waitForPipeline",
194
+ "forceClick",
195
+ "passcode",
196
+ "forceType",
197
+ "check",
198
+ "uncheck",
199
+ "post",
200
+ "get",
201
+ "put",
202
+ "patch",
203
+ "delete",
204
+ "graphql",
205
+ "autoheal",
206
+ "mail.getotp",
207
+ "extract",
208
+ ];
209
+ if (validNakedCommands.map((c) => c.toLowerCase()).includes(lowerIdent)) {
210
+ tokens.push({
211
+ type: TokenType.COMMAND,
212
+ value: ident,
213
+ line: this.line,
214
+ });
215
+ continue;
216
+ }
217
+ else if (ident.includes(".") && !lowerIdent.startsWith("flow.")) {
218
+ tokens.push({
219
+ type: TokenType.ALIAS_COMMAND,
220
+ value: ident,
221
+ line: this.line,
222
+ });
223
+ continue;
224
+ }
225
+ else {
226
+ throw new SyntaxError(`Syntax Error on line ${this.line}: Expected an action command after 'flow.try', but found "${ident}".`);
227
+ }
228
+ }
229
+ if (["given", "when", "then", "and"].includes(lowerIdent)) {
230
+ let tempPos = this.position;
231
+ while (tempPos < this.input.length &&
232
+ (this.input[tempPos] === " " || this.input[tempPos] === "\t"))
233
+ tempPos++;
234
+ if (this.input[tempPos] !== '"') {
235
+ throw new SyntaxError(`Syntax Error on line ${this.line}: Block '${ident}' must be immediately followed by a description in double quotes ("").`);
236
+ }
237
+ tokens.push({ type: TokenType.BLOCK, value: ident, line: this.line });
238
+ }
239
+ else if ([
240
+ "flow.open",
241
+ "flow.type",
242
+ "flow.click",
243
+ "flow.expect",
244
+ "flow.use",
245
+ "flow.save",
246
+ "flow.upload",
247
+ "flow.set",
248
+ "flow.select",
249
+ "flow.drag",
250
+ "flow.switchTo",
251
+ "flow.close",
252
+ "flow.acceptDialog",
253
+ "flow.rejectDialog",
254
+ "flow.injectAudio",
255
+ "flow.waitForPipeline",
256
+ "flow.forceClick",
257
+ "flow.passcode",
258
+ "flow.try",
259
+ "flow.forceType",
260
+ "flow.check",
261
+ "flow.uncheck",
262
+ "flow.post",
263
+ "flow.get",
264
+ "flow.put",
265
+ "flow.patch",
266
+ "flow.delete",
267
+ "flow.graphql",
268
+ "flow.autoheal",
269
+ "flow.mail.getotp",
270
+ "flow.extract",
271
+ ]
272
+ .map((c) => c.toLowerCase())
273
+ .includes(lowerIdent)) {
274
+ tokens.push({
275
+ type: TokenType.COMMAND,
276
+ value: ident,
277
+ line: this.line,
278
+ });
279
+ if (lowerIdent === "flow.try")
280
+ expectingNakedCommand = true;
281
+ }
282
+ else if ([
283
+ "text",
284
+ "visible",
285
+ "value",
286
+ "session",
287
+ "persist",
288
+ "attribute",
289
+ "transcript",
290
+ "contains",
291
+ "placeholder",
292
+ "status",
293
+ "responsetime",
294
+ "reqbody",
295
+ "resbody",
296
+ "reqheader",
297
+ "resheader",
298
+ "cookie",
299
+ "global",
300
+ ].includes(lowerIdent)) {
301
+ tokens.push({
302
+ type: TokenType.MODIFIER,
303
+ value: ident,
304
+ line: this.line,
305
+ });
306
+ }
307
+ else if ([
308
+ "button",
309
+ "link",
310
+ "a",
311
+ "input",
312
+ "field",
313
+ "div",
314
+ "span",
315
+ "image",
316
+ "img",
317
+ ].includes(lowerIdent)) {
318
+ tokens.push({
319
+ type: TokenType.ELEMENT,
320
+ value: ident,
321
+ line: this.line,
322
+ });
323
+ }
324
+ else if ([
325
+ "near",
326
+ "above",
327
+ "under",
328
+ "below",
329
+ "leftof",
330
+ "rightof",
331
+ "inside",
332
+ "in",
333
+ "next",
334
+ ].includes(lowerIdent)) {
335
+ tokens.push({
336
+ type: TokenType.SPATIAL,
337
+ value: ident,
338
+ line: this.line,
339
+ });
340
+ }
341
+ else if ([
342
+ "to",
343
+ "from",
344
+ "into",
345
+ "with",
346
+ "as",
347
+ "equals",
348
+ "lessthan",
349
+ "greaterthan",
350
+ "via",
351
+ "to.be",
352
+ "to.not.be",
353
+ "is",
354
+ "is.not",
355
+ "type.of",
356
+ "format",
357
+ "matches",
358
+ "length",
359
+ "length.greaterthan",
360
+ "length.lessthan",
361
+ "includes",
362
+ "does.not.include",
363
+ "has.key",
364
+ "does.not.have.key",
365
+ "is.empty",
366
+ "not.empty",
367
+ ].includes(lowerIdent)) {
368
+ tokens.push({
369
+ type: TokenType.PREPOSITION,
370
+ value: ident,
371
+ line: this.line,
372
+ });
373
+ }
374
+ else if (ident.includes(".") && !lowerIdent.startsWith("flow.")) {
375
+ tokens.push({
376
+ type: TokenType.ALIAS_COMMAND,
377
+ value: ident,
378
+ line: this.line,
379
+ });
380
+ }
381
+ else {
382
+ tokens.push({
383
+ type: TokenType.IDENTIFIER,
384
+ value: ident,
385
+ line: this.line,
386
+ });
387
+ }
388
+ continue;
389
+ }
390
+ if (char === "/") {
391
+ if (this.position + 1 < this.input.length) {
392
+ const nextChar = this.input[this.position + 1];
393
+ if (nextChar === "/") {
394
+ while (this.position < this.input.length &&
395
+ this.input[this.position] !== "\n") {
396
+ this.position++;
397
+ }
398
+ continue;
399
+ }
400
+ else if (nextChar === "*") {
401
+ this.position += 2;
402
+ while (this.position < this.input.length) {
403
+ if (this.input[this.position] === "\n") {
404
+ this.line++;
405
+ }
406
+ if (this.input[this.position] === "*" &&
407
+ this.position + 1 < this.input.length &&
408
+ this.input[this.position + 1] === "/") {
409
+ this.position += 2;
410
+ break;
411
+ }
412
+ this.position++;
413
+ }
414
+ continue;
415
+ }
416
+ }
417
+ throw new SyntaxError(`Lexer Error on line ${this.line}: Found a stray "/". Only "//" and "/* */" comments are allowed.`);
418
+ }
419
+ throw new SyntaxError(`Lexer Error on line ${this.line}: Unexpected character "${char}"`);
420
+ }
421
+ tokens.push({ type: TokenType.EOF, value: "", line: this.line });
422
+ return tokens;
423
+ }
424
+ }
425
+ exports.Lexer = Lexer;
@@ -0,0 +1,41 @@
1
+ import { Token } from "./lexer";
2
+ export interface DeclarationNode {
3
+ alias: string;
4
+ path: string;
5
+ line: number;
6
+ }
7
+ export interface StepNode {
8
+ id: string;
9
+ blockDescription: string;
10
+ action: string;
11
+ payload: any;
12
+ line: number;
13
+ }
14
+ export interface ScenarioNode {
15
+ name: string;
16
+ declarations: DeclarationNode[];
17
+ steps: StepNode[];
18
+ }
19
+ export interface FeatureNode {
20
+ name: string;
21
+ declarations: DeclarationNode[];
22
+ scenarios: ScenarioNode[];
23
+ }
24
+ export declare class Parser {
25
+ private tokens;
26
+ private position;
27
+ private currentBlockDescription;
28
+ private stepCounter;
29
+ constructor(tokens: Token[]);
30
+ parse(): FeatureNode[];
31
+ private parseDeclaration;
32
+ private parseScenario;
33
+ private parseSmartSelectorPayload;
34
+ private consumeOptionalElement;
35
+ private parseCommand;
36
+ private parseApiModifiers;
37
+ private peek;
38
+ private advance;
39
+ private isAtEnd;
40
+ private consume;
41
+ }