@tayo-dev/rtl 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.
- package/README.md +250 -0
- package/dist/analyzer/mocks/detector.d.ts +59 -0
- package/dist/analyzer/mocks/detector.d.ts.map +1 -0
- package/dist/analyzer/mocks/detector.js +264 -0
- package/dist/analyzer/mocks/detector.js.map +1 -0
- package/dist/analyzer/mocks/target-analyzer.d.ts +92 -0
- package/dist/analyzer/mocks/target-analyzer.d.ts.map +1 -0
- package/dist/analyzer/mocks/target-analyzer.js +305 -0
- package/dist/analyzer/mocks/target-analyzer.js.map +1 -0
- package/dist/analyzer/visual/element-analyzer.d.ts +44 -0
- package/dist/analyzer/visual/element-analyzer.d.ts.map +1 -0
- package/dist/analyzer/visual/element-analyzer.js +176 -0
- package/dist/analyzer/visual/element-analyzer.js.map +1 -0
- package/dist/analyzer/visual/inspector.d.ts +49 -0
- package/dist/analyzer/visual/inspector.d.ts.map +1 -0
- package/dist/analyzer/visual/inspector.js +109 -0
- package/dist/analyzer/visual/inspector.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +13 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +417 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/core/generator.d.ts +32 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/generator.js +173 -0
- package/dist/core/generator.js.map +1 -0
- package/dist/core/js-parser.d.ts +48 -0
- package/dist/core/js-parser.d.ts.map +1 -0
- package/dist/core/js-parser.js +244 -0
- package/dist/core/js-parser.js.map +1 -0
- package/dist/core/mock-intelligence.d.ts +14 -0
- package/dist/core/mock-intelligence.d.ts.map +1 -0
- package/dist/core/mock-intelligence.js +140 -0
- package/dist/core/mock-intelligence.js.map +1 -0
- package/dist/core/orchestrator.d.ts +49 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +315 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/parser.d.ts +9 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +120 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/recording-intelligence.d.ts +15 -0
- package/dist/core/recording-intelligence.d.ts.map +1 -0
- package/dist/core/recording-intelligence.js +178 -0
- package/dist/core/recording-intelligence.js.map +1 -0
- package/dist/core/resolver.d.ts +58 -0
- package/dist/core/resolver.d.ts.map +1 -0
- package/dist/core/resolver.js +291 -0
- package/dist/core/resolver.js.map +1 -0
- package/dist/core/scanner.d.ts +51 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +310 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/scorer.d.ts +8 -0
- package/dist/core/scorer.d.ts.map +1 -0
- package/dist/core/scorer.js +76 -0
- package/dist/core/scorer.js.map +1 -0
- package/dist/core/validator.d.ts +134 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +44 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/core/verifier.d.ts +10 -0
- package/dist/core/verifier.d.ts.map +1 -0
- package/dist/core/verifier.js +30 -0
- package/dist/core/verifier.js.map +1 -0
- package/dist/core/writer.d.ts +15 -0
- package/dist/core/writer.d.ts.map +1 -0
- package/dist/core/writer.js +43 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/generator/mocks/builder.d.ts +47 -0
- package/dist/generator/mocks/builder.d.ts.map +1 -0
- package/dist/generator/mocks/builder.js +335 -0
- package/dist/generator/mocks/builder.js.map +1 -0
- package/dist/generator/transforms/dialog-transform.d.ts +35 -0
- package/dist/generator/transforms/dialog-transform.d.ts.map +1 -0
- package/dist/generator/transforms/dialog-transform.js +293 -0
- package/dist/generator/transforms/dialog-transform.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/learner/analyzer.d.ts +13 -0
- package/dist/learner/analyzer.d.ts.map +1 -0
- package/dist/learner/analyzer.js +484 -0
- package/dist/learner/analyzer.js.map +1 -0
- package/dist/learner/index.d.ts +66 -0
- package/dist/learner/index.d.ts.map +1 -0
- package/dist/learner/index.js +247 -0
- package/dist/learner/index.js.map +1 -0
- package/dist/learner/storage.d.ts +68 -0
- package/dist/learner/storage.d.ts.map +1 -0
- package/dist/learner/storage.js +201 -0
- package/dist/learner/storage.js.map +1 -0
- package/dist/learner/types.d.ts +41 -0
- package/dist/learner/types.d.ts.map +1 -0
- package/dist/learner/types.js +31 -0
- package/dist/learner/types.js.map +1 -0
- package/dist/parser/recorder-parser.d.ts +40 -0
- package/dist/parser/recorder-parser.d.ts.map +1 -0
- package/dist/parser/recorder-parser.js +139 -0
- package/dist/parser/recorder-parser.js.map +1 -0
- package/dist/parser/steps/deduplicator.d.ts +19 -0
- package/dist/parser/steps/deduplicator.d.ts.map +1 -0
- package/dist/parser/steps/deduplicator.js +75 -0
- package/dist/parser/steps/deduplicator.js.map +1 -0
- package/dist/parser/steps/dialog-detector.d.ts +38 -0
- package/dist/parser/steps/dialog-detector.d.ts.map +1 -0
- package/dist/parser/steps/dialog-detector.js +290 -0
- package/dist/parser/steps/dialog-detector.js.map +1 -0
- package/dist/parser/steps/noise-filter.d.ts +21 -0
- package/dist/parser/steps/noise-filter.d.ts.map +1 -0
- package/dist/parser/steps/noise-filter.js +138 -0
- package/dist/parser/steps/noise-filter.js.map +1 -0
- package/dist/scorer/index.d.ts +43 -0
- package/dist/scorer/index.d.ts.map +1 -0
- package/dist/scorer/index.js +82 -0
- package/dist/scorer/index.js.map +1 -0
- package/dist/scorer/post-verify.d.ts +17 -0
- package/dist/scorer/post-verify.d.ts.map +1 -0
- package/dist/scorer/post-verify.js +163 -0
- package/dist/scorer/post-verify.js.map +1 -0
- package/dist/scorer/pre-audit.d.ts +32 -0
- package/dist/scorer/pre-audit.d.ts.map +1 -0
- package/dist/scorer/pre-audit.js +99 -0
- package/dist/scorer/pre-audit.js.map +1 -0
- package/dist/scorer/quality-gates.d.ts +17 -0
- package/dist/scorer/quality-gates.d.ts.map +1 -0
- package/dist/scorer/quality-gates.js +304 -0
- package/dist/scorer/quality-gates.js.map +1 -0
- package/dist/scorer/types.d.ts +27 -0
- package/dist/scorer/types.d.ts.map +1 -0
- package/dist/scorer/types.js +5 -0
- package/dist/scorer/types.js.map +1 -0
- package/dist/templates/test-template.d.ts +21 -0
- package/dist/templates/test-template.d.ts.map +1 -0
- package/dist/templates/test-template.js +92 -0
- package/dist/templates/test-template.js.map +1 -0
- package/dist/types/conventions.d.ts +49 -0
- package/dist/types/conventions.d.ts.map +1 -0
- package/dist/types/conventions.js +13 -0
- package/dist/types/conventions.js.map +1 -0
- package/dist/types/recording.d.ts +143 -0
- package/dist/types/recording.d.ts.map +1 -0
- package/dist/types/recording.js +5 -0
- package/dist/types/recording.js.map +1 -0
- package/dist/types/score.d.ts +18 -0
- package/dist/types/score.d.ts.map +1 -0
- package/dist/types/score.js +2 -0
- package/dist/types/score.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recorder parser - Main entry point for parsing Chrome Recorder exports
|
|
3
|
+
*
|
|
4
|
+
* Pipeline:
|
|
5
|
+
* 1. Parse JSON → NormalizedSteps
|
|
6
|
+
* 2. deduplicateSteps(NormalizedSteps) → DedupedSteps
|
|
7
|
+
* 3. filterNoiseSteps(DedupedSteps) → CleanSteps
|
|
8
|
+
* 4. groupDialogSteps(CleanSteps) → DialogFlows
|
|
9
|
+
* 5. Continue to generation
|
|
10
|
+
*/
|
|
11
|
+
import { readFile } from 'fs/promises';
|
|
12
|
+
import { resolve } from 'path';
|
|
13
|
+
import { deduplicateSteps } from './steps/deduplicator.js';
|
|
14
|
+
import { filterNoiseSteps } from './steps/noise-filter.js';
|
|
15
|
+
import { groupDialogSteps } from './steps/dialog-detector.js';
|
|
16
|
+
let stepIdCounter = 0;
|
|
17
|
+
/**
|
|
18
|
+
* Generate unique step IDs
|
|
19
|
+
*/
|
|
20
|
+
function generateStepId() {
|
|
21
|
+
return `step_${++stepIdCounter}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Map Chrome Recorder step type to action name
|
|
25
|
+
*/
|
|
26
|
+
function getActionName(type) {
|
|
27
|
+
const actionMap = {
|
|
28
|
+
click: 'click',
|
|
29
|
+
fill: 'fill',
|
|
30
|
+
select: 'select',
|
|
31
|
+
scroll: 'scroll',
|
|
32
|
+
assert: 'assert',
|
|
33
|
+
waitForSelector: 'waitForSelector',
|
|
34
|
+
doubleClick: 'doubleClick',
|
|
35
|
+
keyDown: 'keyDown',
|
|
36
|
+
navigate: 'navigate'
|
|
37
|
+
};
|
|
38
|
+
return actionMap[type] || type;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extract selector from Chrome step
|
|
42
|
+
*/
|
|
43
|
+
function extractSelector(step) {
|
|
44
|
+
if (step.selectors && step.selectors.length > 0) {
|
|
45
|
+
const firstSelectorArray = step.selectors[0];
|
|
46
|
+
if (firstSelectorArray && firstSelectorArray.length > 0) {
|
|
47
|
+
return firstSelectorArray[0];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return step.target;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a single Chrome step to internal format
|
|
54
|
+
*/
|
|
55
|
+
function normalizeStep(step, index) {
|
|
56
|
+
const normalized = {
|
|
57
|
+
id: generateStepId(),
|
|
58
|
+
type: step.type,
|
|
59
|
+
action: getActionName(step.type),
|
|
60
|
+
target: step.target || '',
|
|
61
|
+
originalType: step.type,
|
|
62
|
+
selector: extractSelector(step),
|
|
63
|
+
timestamp: step.modifiedTime,
|
|
64
|
+
metadata: {}
|
|
65
|
+
};
|
|
66
|
+
if (step.value !== undefined) {
|
|
67
|
+
normalized.value = step.value;
|
|
68
|
+
}
|
|
69
|
+
if (step.assert) {
|
|
70
|
+
normalized.metadata = {
|
|
71
|
+
...normalized.metadata,
|
|
72
|
+
assertExpression: step.assert.expression
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (step.url) {
|
|
76
|
+
normalized.metadata = {
|
|
77
|
+
...normalized.metadata,
|
|
78
|
+
url: step.url
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse Chrome Recorder JSON file and apply deduplication and noise filtering
|
|
85
|
+
*
|
|
86
|
+
* @param filePath - Path to Chrome Recorder JSON export
|
|
87
|
+
* @returns Normalized recording with cleaned steps
|
|
88
|
+
*/
|
|
89
|
+
export async function parseRecording(filePath) {
|
|
90
|
+
const absolutePath = resolve(process.cwd(), filePath);
|
|
91
|
+
const content = await readFile(absolutePath, 'utf-8');
|
|
92
|
+
let exportData;
|
|
93
|
+
try {
|
|
94
|
+
exportData = JSON.parse(content);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw new Error(`Invalid JSON in ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
98
|
+
}
|
|
99
|
+
if (!exportData.steps || !Array.isArray(exportData.steps)) {
|
|
100
|
+
throw new Error(`Invalid Chrome Recorder export: missing or invalid "steps" array`);
|
|
101
|
+
}
|
|
102
|
+
// Reset step counter for each new recording
|
|
103
|
+
stepIdCounter = 0;
|
|
104
|
+
// Step 1: Normalize steps
|
|
105
|
+
let steps = exportData.steps.map((step, index) => normalizeStep(step, index));
|
|
106
|
+
// Step 2: Deduplicate rapid clicks (must run first)
|
|
107
|
+
steps = deduplicateSteps(steps);
|
|
108
|
+
// Step 3: Filter noise events
|
|
109
|
+
steps = filterNoiseSteps(steps);
|
|
110
|
+
return {
|
|
111
|
+
title: exportData.title || 'Untitled Recording',
|
|
112
|
+
steps,
|
|
113
|
+
rawStepCount: exportData.steps.length,
|
|
114
|
+
url: exportData.settings?.url,
|
|
115
|
+
settings: exportData.settings
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Reset step counter (useful for testing)
|
|
120
|
+
*/
|
|
121
|
+
export function resetStepCounter() {
|
|
122
|
+
stepIdCounter = 0;
|
|
123
|
+
}
|
|
124
|
+
// Re-export for convenience
|
|
125
|
+
export { deduplicateSteps } from './steps/deduplicator.js';
|
|
126
|
+
export { filterNoiseSteps } from './steps/noise-filter.js';
|
|
127
|
+
export { groupDialogSteps, resetDialogIdCounter } from './steps/dialog-detector.js';
|
|
128
|
+
/**
|
|
129
|
+
* Parse recording and extract dialog flows
|
|
130
|
+
*
|
|
131
|
+
* @param filePath - Path to Chrome Recorder JSON export
|
|
132
|
+
* @returns Object with normalized recording and detected dialog flows
|
|
133
|
+
*/
|
|
134
|
+
export async function parseRecordingWithDialogs(filePath) {
|
|
135
|
+
const recording = await parseRecording(filePath);
|
|
136
|
+
const dialogFlows = groupDialogSteps(recording.steps);
|
|
137
|
+
return { recording, dialogFlows };
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=recorder-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder-parser.js","sourceRoot":"","sources":["../../src/parser/recorder-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAmB,MAAM,4BAA4B,CAAC;AAE/E,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;GAEG;AACH,SAAS,cAAc;IACrB,OAAO,QAAQ,EAAE,aAAa,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,IAAc;IACnC,MAAM,SAAS,GAA4C;QACzD,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;QAChB,eAAe,EAAE,iBAAiB;QAClC,WAAW,EAAE,aAAa;QAC1B,OAAO,EAAE,SAAS;QAClB,QAAQ,EAAE,UAAU;KACrB,CAAC;IACF,OAAO,SAAS,CAAC,IAAI,CAAC,IAAK,IAAgC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,IAAgB;IACvC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,kBAAkB,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxD,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,IAAgB,EAAE,KAAa;IACpD,MAAM,UAAU,GAAkB;QAChC,EAAE,EAAE,cAAc,EAAE;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAChC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;QACzB,YAAY,EAAE,IAAI,CAAC,IAAI;QACvB,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC;QAC/B,SAAS,EAAE,IAAI,CAAC,YAAY;QAC5B,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAChC,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,UAAU,CAAC,QAAQ,GAAG;YACpB,GAAG,UAAU,CAAC,QAAQ;YACtB,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;SACzC,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,UAAU,CAAC,QAAQ,GAAG;YACpB,GAAG,UAAU,CAAC,QAAQ;YACtB,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC;IACJ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB;IAEhB,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAEtD,IAAI,UAAgC,CAAC;IACrC,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;IAC9G,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IACtF,CAAC;IAED,4CAA4C;IAC5C,aAAa,GAAG,CAAC,CAAC;IAElB,0BAA0B;IAC1B,IAAI,KAAK,GAAoB,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAChE,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAC3B,CAAC;IAEF,oDAAoD;IACpD,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAEhC,8BAA8B;IAC9B,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO;QACL,KAAK,EAAE,UAAU,CAAC,KAAK,IAAI,oBAAoB;QAC/C,KAAK;QACL,YAAY,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM;QACrC,GAAG,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QAC7B,QAAQ,EAAE,UAAU,CAAC,QAAQ;KAC9B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,aAAa,GAAG,CAAC,CAAC;AACpB,CAAC;AAED,4BAA4B;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGpF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,QAAgB;IAI9D,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACtD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Click deduplicator - removes rapid duplicate clicks on the same element
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* 1. Iterate through steps in order
|
|
6
|
+
* 2. For each click step, look ahead up to 500ms
|
|
7
|
+
* 3. If matching selector found, mark as duplicate
|
|
8
|
+
* 4. Return filtered list preserving original order
|
|
9
|
+
*/
|
|
10
|
+
import type { RecordingStep } from '../../types/recording.js';
|
|
11
|
+
/**
|
|
12
|
+
* Deduplicate rapid clicks on the same element
|
|
13
|
+
*
|
|
14
|
+
* @param steps - Array of recording steps
|
|
15
|
+
* @returns Filtered array with duplicate clicks removed
|
|
16
|
+
*/
|
|
17
|
+
export declare function deduplicateSteps(steps: RecordingStep[]): RecordingStep[];
|
|
18
|
+
export { deduplicateSteps as default };
|
|
19
|
+
//# sourceMappingURL=deduplicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deduplicator.d.ts","sourceRoot":"","sources":["../../../src/parser/steps/deduplicator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAgC9D;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAsCxE;AAED,OAAO,EAAE,gBAAgB,IAAI,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Click deduplicator - removes rapid duplicate clicks on the same element
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* 1. Iterate through steps in order
|
|
6
|
+
* 2. For each click step, look ahead up to 500ms
|
|
7
|
+
* 3. If matching selector found, mark as duplicate
|
|
8
|
+
* 4. Return filtered list preserving original order
|
|
9
|
+
*/
|
|
10
|
+
const RAPID_CLICK_THRESHOLD_MS = 500;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a step is a click type
|
|
13
|
+
*/
|
|
14
|
+
function isClickStep(step) {
|
|
15
|
+
return step.type === 'click' || step.type === 'doubleClick';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if two steps target the same element
|
|
19
|
+
*/
|
|
20
|
+
function hasSameTarget(step1, step2) {
|
|
21
|
+
// Compare by selector or target
|
|
22
|
+
const target1 = step1.selector || step1.target;
|
|
23
|
+
const target2 = step2.selector || step2.target;
|
|
24
|
+
return target1 === target2 && target1 !== '';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if two steps are within the rapid-click threshold
|
|
28
|
+
*/
|
|
29
|
+
function isWithinThreshold(step1, step2) {
|
|
30
|
+
if (step1.timestamp === undefined || step2.timestamp === undefined) {
|
|
31
|
+
// If no timestamps, assume they could be rapid (conservative)
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return Math.abs(step2.timestamp - step1.timestamp) <= RAPID_CLICK_THRESHOLD_MS;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Deduplicate rapid clicks on the same element
|
|
38
|
+
*
|
|
39
|
+
* @param steps - Array of recording steps
|
|
40
|
+
* @returns Filtered array with duplicate clicks removed
|
|
41
|
+
*/
|
|
42
|
+
export function deduplicateSteps(steps) {
|
|
43
|
+
if (!steps || steps.length === 0) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
// Track which steps are duplicates
|
|
47
|
+
const duplicateIds = new Set();
|
|
48
|
+
for (let i = 0; i < steps.length; i++) {
|
|
49
|
+
const currentStep = steps[i];
|
|
50
|
+
// Only consider click steps for deduplication
|
|
51
|
+
if (!isClickStep(currentStep)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Look ahead for potential duplicates
|
|
55
|
+
for (let j = i + 1; j < steps.length; j++) {
|
|
56
|
+
const lookAheadStep = steps[j];
|
|
57
|
+
// Stop looking if we've gone past the threshold time
|
|
58
|
+
if (currentStep.timestamp !== undefined &&
|
|
59
|
+
lookAheadStep.timestamp !== undefined &&
|
|
60
|
+
lookAheadStep.timestamp - currentStep.timestamp > RAPID_CLICK_THRESHOLD_MS) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
// Check if this is a duplicate click on the same target
|
|
64
|
+
if (isClickStep(lookAheadStep) &&
|
|
65
|
+
hasSameTarget(currentStep, lookAheadStep) &&
|
|
66
|
+
isWithinThreshold(currentStep, lookAheadStep)) {
|
|
67
|
+
duplicateIds.add(lookAheadStep.id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Filter out duplicates, preserving original order
|
|
72
|
+
return steps.filter(step => !duplicateIds.has(step.id));
|
|
73
|
+
}
|
|
74
|
+
export { deduplicateSteps as default };
|
|
75
|
+
//# sourceMappingURL=deduplicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deduplicator.js","sourceRoot":"","sources":["../../../src/parser/steps/deduplicator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC;;GAEG;AACH,SAAS,WAAW,CAAC,IAAmB;IACtC,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,KAAoB,EAAE,KAAoB;IAC/D,gCAAgC;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;IAC/C,OAAO,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,EAAE,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,KAAoB,EAAE,KAAoB;IACnE,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACnE,8DAA8D;QAC9D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,wBAAwB,CAAC;AACjF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAsB;IACrD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,mCAAmC;IACnC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAE7B,8CAA8C;QAC9C,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,sCAAsC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAE/B,qDAAqD;YACrD,IAAI,WAAW,CAAC,SAAS,KAAK,SAAS;gBACnC,aAAa,CAAC,SAAS,KAAK,SAAS;gBACrC,aAAa,CAAC,SAAS,GAAG,WAAW,CAAC,SAAS,GAAG,wBAAwB,EAAE,CAAC;gBAC/E,MAAM;YACR,CAAC;YAED,wDAAwD;YACxD,IAAI,WAAW,CAAC,aAAa,CAAC;gBAC1B,aAAa,CAAC,WAAW,EAAE,aAAa,CAAC;gBACzC,iBAAiB,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC;gBAClD,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,OAAO,EAAE,gBAAgB,IAAI,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog flow detector - detects and groups multi-step dialog interactions
|
|
3
|
+
*
|
|
4
|
+
* This module identifies dialog open/close flows and other multi-step interactions
|
|
5
|
+
* as logical units, improving test readability and reducing redundant assertions.
|
|
6
|
+
*
|
|
7
|
+
* Detection patterns:
|
|
8
|
+
* - Open dialog: click on modal trigger → dialog appears
|
|
9
|
+
* - Fill dialog: fill form fields in dialog
|
|
10
|
+
* - Submit dialog: click submit/confirm in dialog
|
|
11
|
+
* - Close dialog: click close/ESC/cancel outside
|
|
12
|
+
*
|
|
13
|
+
* Time window: related steps within 30s are grouped
|
|
14
|
+
*/
|
|
15
|
+
import type { RecordingStep } from '../../types/recording.js';
|
|
16
|
+
export type DialogType = 'modal' | 'drawer' | 'popover' | 'confirm' | 'form';
|
|
17
|
+
export interface DialogFlow {
|
|
18
|
+
id: string;
|
|
19
|
+
type: DialogType;
|
|
20
|
+
triggerStep: RecordingStep;
|
|
21
|
+
contentSteps: RecordingStep[];
|
|
22
|
+
closeStep?: RecordingStep;
|
|
23
|
+
assertionStep?: RecordingStep;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Group recording steps into dialog flows
|
|
28
|
+
*
|
|
29
|
+
* @param steps - Array of recording steps (should be after deduplication and noise filtering)
|
|
30
|
+
* @returns Array of detected dialog flows
|
|
31
|
+
*/
|
|
32
|
+
export declare function groupDialogSteps(steps: RecordingStep[]): DialogFlow[];
|
|
33
|
+
/**
|
|
34
|
+
* Reset dialog ID counter (useful for testing)
|
|
35
|
+
*/
|
|
36
|
+
export declare function resetDialogIdCounter(): void;
|
|
37
|
+
export default groupDialogSteps;
|
|
38
|
+
//# sourceMappingURL=dialog-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-detector.d.ts","sourceRoot":"","sources":["../../../src/parser/steps/dialog-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,0BAA0B,CAAC;AAExE,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,aAAa,CAAC;IAC3B,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAoND;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,UAAU,EAAE,CA4FrE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog flow detector - detects and groups multi-step dialog interactions
|
|
3
|
+
*
|
|
4
|
+
* This module identifies dialog open/close flows and other multi-step interactions
|
|
5
|
+
* as logical units, improving test readability and reducing redundant assertions.
|
|
6
|
+
*
|
|
7
|
+
* Detection patterns:
|
|
8
|
+
* - Open dialog: click on modal trigger → dialog appears
|
|
9
|
+
* - Fill dialog: fill form fields in dialog
|
|
10
|
+
* - Submit dialog: click submit/confirm in dialog
|
|
11
|
+
* - Close dialog: click close/ESC/cancel outside
|
|
12
|
+
*
|
|
13
|
+
* Time window: related steps within 30s are grouped
|
|
14
|
+
*/
|
|
15
|
+
const DIALOG_TIME_WINDOW_MS = 30000; // 30 seconds
|
|
16
|
+
/**
|
|
17
|
+
* Dialog-related selectors that indicate a dialog is opening
|
|
18
|
+
*/
|
|
19
|
+
const DIALOG_TRIGGER_SELECTORS = [
|
|
20
|
+
'[data-testid*="modal"]',
|
|
21
|
+
'[data-testid*="dialog"]',
|
|
22
|
+
'[data-testid*="drawer"]',
|
|
23
|
+
'[data-testid*="popover"]',
|
|
24
|
+
'[aria-haspopup]',
|
|
25
|
+
'[role="dialog"]',
|
|
26
|
+
'[role="dialog"][aria-modal="true"]',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Selectors that indicate a dialog close action
|
|
30
|
+
*/
|
|
31
|
+
const DIALOG_CLOSE_SELECTORS = [
|
|
32
|
+
'.close',
|
|
33
|
+
'[class*="close"]',
|
|
34
|
+
'[aria-label="Close"]',
|
|
35
|
+
'[aria-label="close"]',
|
|
36
|
+
'[aria-label="Close dialog"]',
|
|
37
|
+
'[data-testid*="close"]',
|
|
38
|
+
'[data-testid*="Cancel"]',
|
|
39
|
+
'[role="button"][aria-label="Close"]',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Dialog content selectors that indicate dialog is open
|
|
43
|
+
*/
|
|
44
|
+
const DIALOG_CONTENT_SELECTORS = [
|
|
45
|
+
'[role="dialog"]',
|
|
46
|
+
'[role="dialog"][aria-modal="true"]',
|
|
47
|
+
'[role="alertdialog"]',
|
|
48
|
+
'[data-testid*="modal"]',
|
|
49
|
+
'[data-testid*="dialog"]',
|
|
50
|
+
'[data-testid*="drawer"]',
|
|
51
|
+
'[class*="modal"]',
|
|
52
|
+
'[class*="dialog"]',
|
|
53
|
+
'[class*="drawer"]',
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Check if a selector is a dialog trigger
|
|
57
|
+
*/
|
|
58
|
+
function isDialogTrigger(step) {
|
|
59
|
+
const selector = step.selector || step.target || '';
|
|
60
|
+
const lowerSelector = selector.toLowerCase();
|
|
61
|
+
// Check explicit selectors
|
|
62
|
+
for (const triggerSelector of DIALOG_TRIGGER_SELECTORS) {
|
|
63
|
+
if (selectorMatches(selector, triggerSelector)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Check for common trigger patterns in selector
|
|
68
|
+
const triggerPatterns = ['modal', 'dialog', 'drawer', 'popover', 'open', 'show'];
|
|
69
|
+
return triggerPatterns.some(pattern => lowerSelector.includes(pattern));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if a selector matches a pattern
|
|
73
|
+
*/
|
|
74
|
+
function selectorMatches(selector, pattern) {
|
|
75
|
+
if (!selector || !pattern)
|
|
76
|
+
return false;
|
|
77
|
+
// Handle attribute selectors
|
|
78
|
+
if (pattern.startsWith('[')) {
|
|
79
|
+
return selector.includes(pattern);
|
|
80
|
+
}
|
|
81
|
+
// Simple substring match for class/ID selectors
|
|
82
|
+
return selector.includes(pattern);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if a step is a dialog close action
|
|
86
|
+
*/
|
|
87
|
+
function isDialogCloseAction(step) {
|
|
88
|
+
const action = step.action?.toLowerCase() || '';
|
|
89
|
+
const selector = step.selector || step.target || '';
|
|
90
|
+
const lowerSelector = selector.toLowerCase();
|
|
91
|
+
// Check explicit close selectors
|
|
92
|
+
for (const closeSelector of DIALOG_CLOSE_SELECTORS) {
|
|
93
|
+
if (selectorMatches(selector, closeSelector)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Check for cancel/close in action or selector
|
|
98
|
+
const closePatterns = ['close', 'cancel', 'dismiss', 'esc', 'escape'];
|
|
99
|
+
if (closePatterns.some(pattern => action.includes(pattern) || lowerSelector.includes(pattern))) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// Check for ESC key press
|
|
103
|
+
if (step.type === 'keyDown' && (step.value === 'Escape' || step.value === 'Esc')) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if a step is a click on the dialog overlay (outside the dialog content)
|
|
110
|
+
*/
|
|
111
|
+
function isDialogOverlayClick(step, dialogOpen) {
|
|
112
|
+
if (!dialogOpen)
|
|
113
|
+
return false;
|
|
114
|
+
// Click on body or html typically means clicking outside dialog
|
|
115
|
+
const selector = step.selector || step.target || '';
|
|
116
|
+
const lowerSelector = selector.toLowerCase();
|
|
117
|
+
return (selector === 'body' ||
|
|
118
|
+
selector === 'html' ||
|
|
119
|
+
lowerSelector.includes('overlay') ||
|
|
120
|
+
lowerSelector.includes('backdrop'));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if a step appears to be filling dialog content
|
|
124
|
+
*/
|
|
125
|
+
function isDialogContentStep(step) {
|
|
126
|
+
// Fill, select, or keyDown in form elements
|
|
127
|
+
if (step.type === 'fill' || step.type === 'select') {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// KeyDown for typing in input fields
|
|
131
|
+
if (step.type === 'keyDown') {
|
|
132
|
+
const selector = step.selector || step.target || '';
|
|
133
|
+
const lowerSelector = selector.toLowerCase();
|
|
134
|
+
// Input, textarea, or contentEditable elements
|
|
135
|
+
return (lowerSelector.includes('input') ||
|
|
136
|
+
lowerSelector.includes('textarea') ||
|
|
137
|
+
lowerSelector.includes('contenteditable'));
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if step appears to be an assertion about dialog state
|
|
143
|
+
*/
|
|
144
|
+
function isDialogAssertion(step) {
|
|
145
|
+
if (step.type !== 'assert' && step.type !== 'waitForSelector') {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const selector = step.selector || step.target || '';
|
|
149
|
+
const lowerSelector = selector.toLowerCase();
|
|
150
|
+
// Check if assertion targets dialog elements
|
|
151
|
+
for (const contentSelector of DIALOG_CONTENT_SELECTORS) {
|
|
152
|
+
if (selectorMatches(selector, contentSelector)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Check for dialog-related keywords
|
|
157
|
+
const dialogKeywords = ['modal', 'dialog', 'drawer', 'popover', 'visible', 'open'];
|
|
158
|
+
return dialogKeywords.some(keyword => lowerSelector.includes(keyword));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Determine the dialog type based on content
|
|
162
|
+
*/
|
|
163
|
+
function inferDialogType(steps) {
|
|
164
|
+
// Check selectors for type hints
|
|
165
|
+
for (const step of steps) {
|
|
166
|
+
const selector = step.selector || step.target || '';
|
|
167
|
+
const lowerSelector = selector.toLowerCase();
|
|
168
|
+
if (lowerSelector.includes('drawer'))
|
|
169
|
+
return 'drawer';
|
|
170
|
+
if (lowerSelector.includes('popover'))
|
|
171
|
+
return 'popover';
|
|
172
|
+
if (lowerSelector.includes('confirm') || lowerSelector.includes('alert'))
|
|
173
|
+
return 'confirm';
|
|
174
|
+
}
|
|
175
|
+
// Default to modal
|
|
176
|
+
return 'modal';
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if two steps are within the dialog time window
|
|
180
|
+
*/
|
|
181
|
+
function isWithinTimeWindow(step1, step2) {
|
|
182
|
+
if (step1.timestamp === undefined || step2.timestamp === undefined) {
|
|
183
|
+
// If no timestamps, assume they could be related (conservative)
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return Math.abs(step2.timestamp - step1.timestamp) <= DIALOG_TIME_WINDOW_MS;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate a unique ID for a dialog flow
|
|
190
|
+
*/
|
|
191
|
+
let dialogIdCounter = 0;
|
|
192
|
+
function generateDialogId() {
|
|
193
|
+
return `dialog_${++dialogIdCounter}`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Group recording steps into dialog flows
|
|
197
|
+
*
|
|
198
|
+
* @param steps - Array of recording steps (should be after deduplication and noise filtering)
|
|
199
|
+
* @returns Array of detected dialog flows
|
|
200
|
+
*/
|
|
201
|
+
export function groupDialogSteps(steps) {
|
|
202
|
+
if (!steps || steps.length === 0) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
const dialogFlows = [];
|
|
206
|
+
let currentDialog = null;
|
|
207
|
+
let dialogOpen = false;
|
|
208
|
+
for (let i = 0; i < steps.length; i++) {
|
|
209
|
+
const step = steps[i];
|
|
210
|
+
// Skip if not a relevant step type
|
|
211
|
+
if (!['click', 'fill', 'select', 'keyDown', 'assert', 'waitForSelector'].includes(step.type)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Case 1: Dialog trigger click - start a new dialog flow
|
|
215
|
+
if (step.type === 'click' && isDialogTrigger(step)) {
|
|
216
|
+
// Close any existing dialog flow
|
|
217
|
+
if (currentDialog) {
|
|
218
|
+
dialogFlows.push(currentDialog);
|
|
219
|
+
}
|
|
220
|
+
currentDialog = {
|
|
221
|
+
id: generateDialogId(),
|
|
222
|
+
type: inferDialogType([step]),
|
|
223
|
+
triggerStep: step,
|
|
224
|
+
contentSteps: [],
|
|
225
|
+
timestamp: step.timestamp || Date.now(),
|
|
226
|
+
};
|
|
227
|
+
dialogOpen = true;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// If we have an active dialog flow
|
|
231
|
+
if (currentDialog) {
|
|
232
|
+
// Case 2: Dialog close action - end the current flow
|
|
233
|
+
if (isDialogCloseAction(step) || isDialogOverlayClick(step, dialogOpen)) {
|
|
234
|
+
currentDialog.closeStep = step;
|
|
235
|
+
dialogFlows.push(currentDialog);
|
|
236
|
+
currentDialog = null;
|
|
237
|
+
dialogOpen = false;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// Case 3: Dialog content step - add to current flow
|
|
241
|
+
if (isDialogContentStep(step)) {
|
|
242
|
+
// Check time window - if too far, might be separate interaction
|
|
243
|
+
if (isWithinTimeWindow(currentDialog.triggerStep, step)) {
|
|
244
|
+
currentDialog.contentSteps.push(step);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Case 4: Dialog assertion - mark for verification
|
|
249
|
+
if (isDialogAssertion(step)) {
|
|
250
|
+
currentDialog.assertionStep = step;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
// Case 5: Non-dialog click - might close dialog and end flow
|
|
254
|
+
if (step.type === 'click' && !isDialogTrigger(step)) {
|
|
255
|
+
// Check if this might be closing the dialog
|
|
256
|
+
if (dialogOpen) {
|
|
257
|
+
currentDialog.closeStep = step;
|
|
258
|
+
dialogFlows.push(currentDialog);
|
|
259
|
+
currentDialog = null;
|
|
260
|
+
dialogOpen = false;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Check time window - if step is too far from dialog start, end the flow
|
|
265
|
+
if (!isWithinTimeWindow(currentDialog.triggerStep, step)) {
|
|
266
|
+
if (currentDialog.contentSteps.length > 0 || currentDialog.closeStep) {
|
|
267
|
+
dialogFlows.push(currentDialog);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// No meaningful content, discard
|
|
271
|
+
}
|
|
272
|
+
currentDialog = null;
|
|
273
|
+
dialogOpen = false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Don't forget the last dialog if still open
|
|
278
|
+
if (currentDialog) {
|
|
279
|
+
dialogFlows.push(currentDialog);
|
|
280
|
+
}
|
|
281
|
+
return dialogFlows;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Reset dialog ID counter (useful for testing)
|
|
285
|
+
*/
|
|
286
|
+
export function resetDialogIdCounter() {
|
|
287
|
+
dialogIdCounter = 0;
|
|
288
|
+
}
|
|
289
|
+
export default groupDialogSteps;
|
|
290
|
+
//# sourceMappingURL=dialog-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-detector.js","sourceRoot":"","sources":["../../../src/parser/steps/dialog-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noise event filter - removes irrelevant events from recordings
|
|
3
|
+
*
|
|
4
|
+
* Filters out:
|
|
5
|
+
* - dblClick events (double clicks)
|
|
6
|
+
* - mousemove/mouseover/mouseout (cursor wandering)
|
|
7
|
+
* - Accidental scroll events (scroll with no action within 2s)
|
|
8
|
+
*
|
|
9
|
+
* Preserves:
|
|
10
|
+
* - click, fill, select, change, navigate, keyPress, assert
|
|
11
|
+
*/
|
|
12
|
+
import type { RecordingStep } from '../../types/recording.js';
|
|
13
|
+
/**
|
|
14
|
+
* Filter noise events from recording steps
|
|
15
|
+
*
|
|
16
|
+
* @param steps - Array of recording steps
|
|
17
|
+
* @returns Filtered array with noise events removed
|
|
18
|
+
*/
|
|
19
|
+
export declare function filterNoiseSteps(steps: RecordingStep[]): RecordingStep[];
|
|
20
|
+
export { filterNoiseSteps as default };
|
|
21
|
+
//# sourceMappingURL=noise-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noise-filter.d.ts","sourceRoot":"","sources":["../../../src/parser/steps/noise-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,0BAA0B,CAAC;AAqGxE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAuCxE;AAED,OAAO,EAAE,gBAAgB,IAAI,OAAO,EAAE,CAAC"}
|