flowstride 1.0.17 ā 1.0.19
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/dist/adapters/web/playwright.d.ts +3 -0
- package/dist/adapters/web/playwright.js +67 -24
- package/dist/cli/commands/init.js +7 -2
- package/dist/cli/commands/open.d.ts +1 -0
- package/dist/cli/commands/open.js +12 -0
- package/dist/cli/index.js +8 -1
- package/dist/cli/manual_server/workspace_server.d.ts +1 -0
- package/dist/cli/manual_server/workspace_server.js +751 -0
- package/dist/parser/lexer.js +4 -0
- package/dist/parser/parser.js +19 -3
- package/dist/runner/flow_worker.js +29 -9
- package/package.json +1 -1
- package/src/ui/dist/assets/index-DIC22ZK_.js +638 -0
- package/src/ui/dist/assets/workspace_engine_bg-BVlpAZ1H.wasm +0 -0
- package/src/ui/dist/index.html +1 -1
- package/src/ui/dist/assets/index-BCdexgt5.js +0 -270
|
@@ -25,6 +25,8 @@ export declare class WebAdapter {
|
|
|
25
25
|
synthesizeCurl(method: string, url: string, headers: Record<string, string>, postData: string | null): string;
|
|
26
26
|
private checkAmbiguity;
|
|
27
27
|
close(targetText: string): Promise<void>;
|
|
28
|
+
extractText(selector: string, elementType?: string): Promise<string>;
|
|
29
|
+
extractValue(selector: string, elementType?: string): Promise<string>;
|
|
28
30
|
click(selector: string, elementType?: string): Promise<void>;
|
|
29
31
|
hover(selector: string, elementType?: string): Promise<void>;
|
|
30
32
|
type(selector: string, value: string, elementType?: string): Promise<void>;
|
|
@@ -43,6 +45,7 @@ export declare class WebAdapter {
|
|
|
43
45
|
expectValue(selector: string, val: string, elementType?: string): Promise<void>;
|
|
44
46
|
expectTranscript(expectedText: string): Promise<void>;
|
|
45
47
|
getAttribute(selector: string, attr: string, elementType?: string): Promise<string | null>;
|
|
48
|
+
private translateCoordinateSelector;
|
|
46
49
|
private isRawCssSelector;
|
|
47
50
|
private buildHeuristicSelector;
|
|
48
51
|
private getLocator;
|
|
@@ -62,13 +62,13 @@ class WebAdapter {
|
|
|
62
62
|
? [
|
|
63
63
|
"--use-fake-ui-for-media-stream",
|
|
64
64
|
"--use-fake-device-for-media-stream",
|
|
65
|
-
"--disable-blink-features=AutomationControlled",
|
|
65
|
+
"--disable-blink-features=AutomationControlled",
|
|
66
66
|
]
|
|
67
67
|
: [
|
|
68
68
|
"--window-size=1280,800",
|
|
69
69
|
"--use-fake-ui-for-media-stream",
|
|
70
70
|
"--use-fake-device-for-media-stream",
|
|
71
|
-
"--disable-blink-features=AutomationControlled",
|
|
71
|
+
"--disable-blink-features=AutomationControlled",
|
|
72
72
|
];
|
|
73
73
|
if (audioInjectionPath) {
|
|
74
74
|
launchArgs.push(`--use-file-for-fake-audio-capture=${audioInjectionPath}`);
|
|
@@ -390,6 +390,18 @@ class WebAdapter {
|
|
|
390
390
|
await this.page.mouse.up();
|
|
391
391
|
await this.page.waitForTimeout(300);
|
|
392
392
|
}
|
|
393
|
+
async extractText(selector, elementType) {
|
|
394
|
+
const loc = await this.getLocator(selector, elementType, false, false);
|
|
395
|
+
const targetLoc = await this.checkAmbiguity(loc, selector);
|
|
396
|
+
await targetLoc.waitFor({ state: "visible" });
|
|
397
|
+
return (await targetLoc.innerText()) || "";
|
|
398
|
+
}
|
|
399
|
+
async extractValue(selector, elementType) {
|
|
400
|
+
const loc = await this.getLocator(selector, elementType, false, false);
|
|
401
|
+
const targetLoc = await this.checkAmbiguity(loc, selector);
|
|
402
|
+
await targetLoc.waitFor({ state: "visible" });
|
|
403
|
+
return (await targetLoc.inputValue()) || "";
|
|
404
|
+
}
|
|
393
405
|
async click(selector, elementType) {
|
|
394
406
|
const loc = await this.getLocator(selector, elementType, false, false);
|
|
395
407
|
const targetLoc = await this.checkAmbiguity(loc, selector);
|
|
@@ -402,7 +414,6 @@ class WebAdapter {
|
|
|
402
414
|
const targetLoc = await this.checkAmbiguity(loc, selector);
|
|
403
415
|
await targetLoc.waitFor({ state: "visible" });
|
|
404
416
|
await targetLoc.scrollIntoViewIfNeeded();
|
|
405
|
-
// 1. Accessibility WAI-ARIA Hack (Focus)
|
|
406
417
|
await targetLoc.focus({ timeout: 3000 }).catch(() => { });
|
|
407
418
|
await targetLoc
|
|
408
419
|
.evaluate((node) => {
|
|
@@ -410,21 +421,17 @@ class WebAdapter {
|
|
|
410
421
|
node.focus();
|
|
411
422
|
})
|
|
412
423
|
.catch(() => { });
|
|
413
|
-
// 2. Get Exact Center Coordinates for Physical Move
|
|
414
424
|
let box = null;
|
|
415
425
|
try {
|
|
416
426
|
box = await targetLoc.boundingBox({ timeout: 3000 });
|
|
417
427
|
}
|
|
418
|
-
catch (e) { }
|
|
428
|
+
catch (e) { }
|
|
419
429
|
if (box) {
|
|
420
430
|
const centerX = box.x + box.width / 2;
|
|
421
431
|
const centerY = box.y + box.height / 2;
|
|
422
|
-
// Physically drag the mouse to the element to satisfy Hover Intent APIs
|
|
423
432
|
await this.page.mouse.move(centerX, centerY, { steps: 5 });
|
|
424
433
|
}
|
|
425
|
-
// 3. Playwright Native Hover Backup (Caught to prevent mutation crashes)
|
|
426
434
|
await targetLoc.hover({ force: true, timeout: 3000 }).catch(() => { });
|
|
427
|
-
// 4. The Ultimate Wakeup Call: Synthesize Full Coordinate-Loaded Events
|
|
428
435
|
await targetLoc
|
|
429
436
|
.evaluate((node) => {
|
|
430
437
|
const rect = node.getBoundingClientRect();
|
|
@@ -450,11 +457,9 @@ class WebAdapter {
|
|
|
450
457
|
});
|
|
451
458
|
})
|
|
452
459
|
.catch(() => { });
|
|
453
|
-
// 5. We perform a programmatic click that won't trigger W3C navigation if handled by React toggles
|
|
454
460
|
await targetLoc
|
|
455
461
|
.click({ delay: 50, force: true, timeout: 3000 })
|
|
456
462
|
.catch(() => { });
|
|
457
|
-
// 6. The Paint Buffer
|
|
458
463
|
await this.page.waitForTimeout(600);
|
|
459
464
|
}
|
|
460
465
|
async type(selector, value, elementType) {
|
|
@@ -902,7 +907,7 @@ class WebAdapter {
|
|
|
902
907
|
.replace(/[^\w\s]/g, "")
|
|
903
908
|
.replace(/\s+/g, " ");
|
|
904
909
|
if (cleanBody.includes(cleanExpected)) {
|
|
905
|
-
return;
|
|
910
|
+
return;
|
|
906
911
|
}
|
|
907
912
|
await this.page.waitForTimeout(500);
|
|
908
913
|
}
|
|
@@ -912,9 +917,32 @@ class WebAdapter {
|
|
|
912
917
|
const loc = await this.getLocator(selector, elementType, true, true);
|
|
913
918
|
return await loc.first().getAttribute(attr);
|
|
914
919
|
}
|
|
920
|
+
translateCoordinateSelector(selector) {
|
|
921
|
+
const cleanSelector = selector.replace(/^['"]|['"]$/g, "").trim();
|
|
922
|
+
if (!cleanSelector)
|
|
923
|
+
return null;
|
|
924
|
+
const parts = cleanSelector.split(/\s+/);
|
|
925
|
+
const validTags = ["table", "tr", "th", "td"];
|
|
926
|
+
const isCoordinatePath = parts.every((part) => validTags.some((tag) => part.toLowerCase().startsWith(tag)));
|
|
927
|
+
if (!isCoordinatePath)
|
|
928
|
+
return null;
|
|
929
|
+
let translated = "";
|
|
930
|
+
for (let i = 0; i < parts.length; i++) {
|
|
931
|
+
const part = parts[i];
|
|
932
|
+
const match = part.match(/^([a-z]+)(?:\[(\d+)\])?$/i);
|
|
933
|
+
if (!match)
|
|
934
|
+
return null;
|
|
935
|
+
const tag = match[1];
|
|
936
|
+
const index = match[2] ? ` >> nth=${match[2]}` : "";
|
|
937
|
+
translated += (i > 0 ? " >> " : "") + tag + index;
|
|
938
|
+
}
|
|
939
|
+
return translated;
|
|
940
|
+
}
|
|
915
941
|
isRawCssSelector(selector) {
|
|
916
942
|
if (/\b(near|above|under|below|leftOf|rightOf|inside|in)\s+['"]/i.test(selector))
|
|
917
943
|
return false;
|
|
944
|
+
if (selector.includes(">> nth="))
|
|
945
|
+
return true;
|
|
918
946
|
if (/^[#\.\[]/.test(selector))
|
|
919
947
|
return true;
|
|
920
948
|
if (/^[a-zA-Z0-9]+\s*[\.\#:][a-zA-Z0-9_\-]+/.test(selector))
|
|
@@ -954,6 +982,9 @@ class WebAdapter {
|
|
|
954
982
|
"iframe",
|
|
955
983
|
"img",
|
|
956
984
|
"svg",
|
|
985
|
+
"tr",
|
|
986
|
+
"th",
|
|
987
|
+
"td",
|
|
957
988
|
];
|
|
958
989
|
if (htmlTags.includes(selector.toLowerCase()))
|
|
959
990
|
return true;
|
|
@@ -995,27 +1026,35 @@ class WebAdapter {
|
|
|
995
1026
|
return `:is(${attributes}, ${partialActionable})`;
|
|
996
1027
|
}
|
|
997
1028
|
async getLocator(rawSelector, elementType, isForceAction = false, isAssertionOrAnchor = false, fuzzy = false) {
|
|
998
|
-
|
|
999
|
-
|
|
1029
|
+
const translatedRaw = this.translateCoordinateSelector(rawSelector);
|
|
1030
|
+
const processingSelector = translatedRaw || rawSelector;
|
|
1031
|
+
if (this.isRawCssSelector(processingSelector.trim())) {
|
|
1032
|
+
let loc = this.page.locator(processingSelector);
|
|
1000
1033
|
if (!isForceAction)
|
|
1001
1034
|
loc = loc.filter({ visible: true });
|
|
1002
1035
|
return loc;
|
|
1003
1036
|
}
|
|
1004
|
-
let target =
|
|
1037
|
+
let target = processingSelector;
|
|
1005
1038
|
let relation = "";
|
|
1006
1039
|
let anchor = "";
|
|
1007
1040
|
const spatialRegex = /\b(near|above|under|below|leftOf|rightOf|inside|in)\s+(['"].*?['"])/i;
|
|
1008
|
-
const match =
|
|
1041
|
+
const match = processingSelector.match(spatialRegex);
|
|
1009
1042
|
if (match) {
|
|
1010
|
-
target =
|
|
1043
|
+
target = processingSelector.substring(0, match.index).trim();
|
|
1011
1044
|
relation = match[1].toLowerCase();
|
|
1012
1045
|
anchor = match[2];
|
|
1013
1046
|
}
|
|
1047
|
+
const translatedTarget = this.translateCoordinateSelector(target);
|
|
1048
|
+
if (translatedTarget) {
|
|
1049
|
+
target = translatedTarget;
|
|
1050
|
+
}
|
|
1014
1051
|
let arrayIndex = null;
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1052
|
+
if (!translatedTarget) {
|
|
1053
|
+
const indexMatch = target.match(/\[(\d+)\]/);
|
|
1054
|
+
if (indexMatch) {
|
|
1055
|
+
arrayIndex = parseInt(indexMatch[1], 10);
|
|
1056
|
+
target = target.replace(indexMatch[0], "").trim();
|
|
1057
|
+
}
|
|
1019
1058
|
}
|
|
1020
1059
|
const targetClean = target.replace(/^['"]|['"]$/g, "");
|
|
1021
1060
|
const targetIsRaw = this.isRawCssSelector(targetClean);
|
|
@@ -1025,10 +1064,12 @@ class WebAdapter {
|
|
|
1025
1064
|
let alternativeSelector = "";
|
|
1026
1065
|
if (relation && anchor) {
|
|
1027
1066
|
const anchorClean = anchor.replace(/^['"]|['"]$/g, "");
|
|
1028
|
-
const
|
|
1067
|
+
const translatedAnchor = this.translateCoordinateSelector(anchorClean);
|
|
1068
|
+
const finalAnchorClean = translatedAnchor || anchorClean;
|
|
1069
|
+
const anchorIsRaw = this.isRawCssSelector(finalAnchorClean);
|
|
1029
1070
|
const anchorSelector = anchorIsRaw
|
|
1030
|
-
?
|
|
1031
|
-
: this.buildHeuristicSelector(
|
|
1071
|
+
? finalAnchorClean
|
|
1072
|
+
: this.buildHeuristicSelector(finalAnchorClean, undefined, true, fuzzy);
|
|
1032
1073
|
const anchorLoc = this.page.locator(anchorSelector).first();
|
|
1033
1074
|
try {
|
|
1034
1075
|
if ((await anchorLoc.count()) > 0) {
|
|
@@ -1055,7 +1096,9 @@ class WebAdapter {
|
|
|
1055
1096
|
break;
|
|
1056
1097
|
case "inside":
|
|
1057
1098
|
case "in":
|
|
1058
|
-
|
|
1099
|
+
if (!anchorSelector.includes("nth=")) {
|
|
1100
|
+
alternativeSelector = `${anchorSelector}:has-text("${targetClean}")`;
|
|
1101
|
+
}
|
|
1059
1102
|
finalSelector = `${anchorSelector} >> ${finalSelector}`;
|
|
1060
1103
|
break;
|
|
1061
1104
|
}
|
|
@@ -46,17 +46,22 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
46
46
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
47
47
|
async function initAction() {
|
|
48
48
|
const root = process.cwd();
|
|
49
|
-
|
|
49
|
+
// Added 'manual_flows' to strictly isolate manual test designs
|
|
50
|
+
const folders = ["flows", "manual_flows", "reports", "plugins", "fixtures"];
|
|
50
51
|
const sampleFlowPath = path.join(root, "flows", "example.flow");
|
|
51
52
|
const fixturesReadmePath = path.join(root, "fixtures", "README.md");
|
|
52
53
|
const gitignorePath = path.join(root, ".gitignore");
|
|
53
|
-
console.log("\n[Info]
|
|
54
|
+
console.log("\n[Info] Initialising new workspace...");
|
|
54
55
|
// 1. Create Directories
|
|
55
56
|
folders.forEach((folder) => {
|
|
56
57
|
const dirPath = path.join(root, folder);
|
|
57
58
|
if (!fs.existsSync(dirPath)) {
|
|
58
59
|
fs.mkdirSync(dirPath);
|
|
59
60
|
console.log(`[OK] Created directory: ${folder}/`);
|
|
61
|
+
// Securely ensure the manual_flows directory is strictly tracked by Git even when empty
|
|
62
|
+
if (folder === "manual_flows") {
|
|
63
|
+
fs.writeFileSync(path.join(dirPath, ".gitkeep"), "");
|
|
64
|
+
}
|
|
60
65
|
}
|
|
61
66
|
else {
|
|
62
67
|
console.log(`[Info] Directory already exists: ${folder}/`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openAction(): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
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.openAction = openAction;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const workspace_server_1 = require("../manual_server/workspace_server");
|
|
9
|
+
async function openAction() {
|
|
10
|
+
console.log(chalk_1.default.cyan("\nšØ Flowstride: Launching Local Manual Test Design Workspace..."));
|
|
11
|
+
await (0, workspace_server_1.startManualWorkspaceServer)();
|
|
12
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -41,6 +41,7 @@ const commander_1 = require("commander");
|
|
|
41
41
|
const run_1 = require("./commands/run");
|
|
42
42
|
const report_1 = require("./commands/report");
|
|
43
43
|
const init_1 = require("./commands/init");
|
|
44
|
+
const open_1 = require("./commands/open");
|
|
44
45
|
const fs = __importStar(require("fs"));
|
|
45
46
|
const path = __importStar(require("path"));
|
|
46
47
|
const os = __importStar(require("os"));
|
|
@@ -53,7 +54,7 @@ const API_BASE = process.env.FLOWSTRIDE_API_URL || "https://cloud.flowstride.io"
|
|
|
53
54
|
program
|
|
54
55
|
.name("flowstride")
|
|
55
56
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
56
|
-
.version("1.0.
|
|
57
|
+
.version("1.0.19");
|
|
57
58
|
program
|
|
58
59
|
.command("init")
|
|
59
60
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
@@ -374,4 +375,10 @@ program
|
|
|
374
375
|
.action(async () => {
|
|
375
376
|
await (0, report_1.reportAction)();
|
|
376
377
|
});
|
|
378
|
+
program
|
|
379
|
+
.command("open")
|
|
380
|
+
.description("Launch the local manual QA UI Workspace")
|
|
381
|
+
.action(async () => {
|
|
382
|
+
await (0, open_1.openAction)();
|
|
383
|
+
});
|
|
377
384
|
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startManualWorkspaceServer(): Promise<void>;
|