@sungen/driver-ui 3.2.5 → 3.2.7
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sungen/driver-ui",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.7",
|
|
4
4
|
"description": "Sungen UI capability (Playwright) — step patterns, viewpoint gate, and phase hooks. Plugs into @sun-asterisk/sungen via the capability SPI.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"author": "eqe team (engineer & quality) — Sun Asterisk",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@sun-asterisk/sungen": "3.2.
|
|
16
|
+
"@sun-asterisk/sungen": "3.2.7"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"@playwright/test": "^1.57.0"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParsedStep } from '@sun-asterisk/sungen';
|
|
2
2
|
import { StepPattern, StepTemplateData } from '@sun-asterisk/sungen';
|
|
3
|
+
import { pathToRegexSource } from '../utils/url-path-regex';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Assertion patterns: visibility, text content, state, attributes
|
|
@@ -73,7 +74,10 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
73
74
|
step.text.includes('sees')) &&
|
|
74
75
|
step.elementType === 'page',
|
|
75
76
|
resolver: (step, context): StepTemplateData => {
|
|
76
|
-
|
|
77
|
+
// Fallback is the feature's declared Path (context.featurePath) — the page under test.
|
|
78
|
+
// `step.featurePath` is never populated by the parser, so it always degraded to '/'.
|
|
79
|
+
const fallbackPath = context.featurePath || '/';
|
|
80
|
+
let path = fallbackPath;
|
|
77
81
|
|
|
78
82
|
// If selector is present, extract path from selector's value attribute
|
|
79
83
|
if (step.selectorRef) {
|
|
@@ -84,16 +88,20 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
84
88
|
step.elementType,
|
|
85
89
|
step.nth
|
|
86
90
|
);
|
|
87
|
-
//
|
|
88
|
-
|
|
91
|
+
// Only a `page`-type entry carries a URL in `value`. Selector keys can collide across
|
|
92
|
+
// types (a single `読取結果一覧` entry of type `role` reused for a page assertion), and a
|
|
93
|
+
// key-only lookup happily returns that button entry — whose `value` ('button') is not a
|
|
94
|
+
// URL. Guard on the entry kind: use `value` only for a page selector, else fall back to
|
|
95
|
+
// the feature path rather than compiling `toHaveURL(/button/)`.
|
|
96
|
+
path = resolved.selectorType === 'page' ? (resolved.value || fallbackPath) : fallbackPath;
|
|
89
97
|
} catch (error) {
|
|
90
98
|
// Fallback to feature path if selector not found
|
|
91
|
-
path =
|
|
99
|
+
path = fallbackPath;
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
// Convert path to regex-safe string
|
|
96
|
-
const pathRegex = path
|
|
103
|
+
// Convert path to regex-safe string (dynamic :id segments → [^/]+ wildcard)
|
|
104
|
+
const pathRegex = pathToRegexSource(path);
|
|
97
105
|
return {
|
|
98
106
|
templateName: 'page-assertion',
|
|
99
107
|
data: { pathRegex },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParsedStep } from '@sun-asterisk/sungen';
|
|
2
2
|
import { StepPattern } from '@sun-asterisk/sungen';
|
|
3
|
+
import { escapeRegex, pathToRegexSource } from '../utils/url-path-regex';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Extract Playwright waitFor state from step text.
|
|
@@ -296,14 +297,20 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
296
297
|
matcher: (step: ParsedStep) =>
|
|
297
298
|
(step.text.includes('wait for') || step.text.includes('waits for')) && step.elementType === 'page',
|
|
298
299
|
resolver: (step, context) => {
|
|
299
|
-
|
|
300
|
+
// Fallback is the feature's declared Path (context.featurePath) — `step.featurePath` is
|
|
301
|
+
// never populated by the parser, so it always degraded to '/'.
|
|
302
|
+
const fallbackPath = context.featurePath || '/';
|
|
303
|
+
let path = fallbackPath;
|
|
300
304
|
|
|
301
305
|
if (step.selectorRef) {
|
|
302
306
|
try {
|
|
303
307
|
const resolved = context.selectorResolver.resolveSelector(
|
|
304
308
|
step.selectorRef, context.featureName, step.elementType, step.nth
|
|
305
309
|
);
|
|
306
|
-
|
|
310
|
+
// Only a `page`-type entry carries a URL in `value`. A key collision with a non-page
|
|
311
|
+
// entry (e.g. a role/button sharing the key) must fall back to featurePath, never feed
|
|
312
|
+
// its value ('button') into waitForURL. See page-assertion for the mirror guard.
|
|
313
|
+
path = resolved.selectorType === 'page' ? (resolved.value || fallbackPath) : fallbackPath;
|
|
307
314
|
} catch (error) {
|
|
308
315
|
// fallback to featurePath
|
|
309
316
|
}
|
|
@@ -313,11 +320,12 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
313
320
|
let pathRegex: string;
|
|
314
321
|
if (isAbsoluteUrl) {
|
|
315
322
|
const url = new URL(path);
|
|
316
|
-
const hostEscaped = url.hostname
|
|
317
|
-
|
|
323
|
+
const hostEscaped = escapeRegex(url.hostname);
|
|
324
|
+
// Dynamic :id segments in the path → [^/]+ wildcard (host stays literal)
|
|
325
|
+
const pathEscaped = url.pathname !== '/' ? pathToRegexSource(url.pathname) : '';
|
|
318
326
|
pathRegex = hostEscaped + pathEscaped;
|
|
319
327
|
} else {
|
|
320
|
-
pathRegex = path
|
|
328
|
+
pathRegex = pathToRegexSource(path);
|
|
321
329
|
}
|
|
322
330
|
|
|
323
331
|
return {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-path → RegExp-source conversion for page URL assertions (`toHaveURL`) and
|
|
3
|
+
* navigation waits (`waitForURL`).
|
|
4
|
+
*
|
|
5
|
+
* A selector's `value` may carry dynamic route placeholders (`:id`, `:receptionId`,
|
|
6
|
+
* …), e.g. `/cn22/view/so/ocr/results/:id`. A live URL populates those segments with
|
|
7
|
+
* real values (`…/results/rec-dummy-000017`), so a fully-escaped literal pattern would
|
|
8
|
+
* never match. Placeholder segments become a single-segment wildcard (`[^/]+`); every
|
|
9
|
+
* other segment is escaped so its regex metacharacters stay literal.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const REGEX_SPECIALS = /[.*+?^${}()|[\]\\/]/g;
|
|
13
|
+
|
|
14
|
+
/** Escape a literal string for safe use inside a RegExp source. */
|
|
15
|
+
export function escapeRegex(text: string): string {
|
|
16
|
+
return text.replace(REGEX_SPECIALS, '\\$&');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert a URL path into a RegExp source string, turning dynamic route
|
|
21
|
+
* placeholders (`:name`) into `[^/]+` wildcards while escaping literal segments.
|
|
22
|
+
* `/` is emitted as the escaped `\/` so the result drops straight into `/…/`.
|
|
23
|
+
*/
|
|
24
|
+
export function pathToRegexSource(path: string): string {
|
|
25
|
+
return path
|
|
26
|
+
.split('/')
|
|
27
|
+
.map((segment) =>
|
|
28
|
+
/^:[A-Za-z_][A-Za-z0-9_]*$/.test(segment) ? '[^/]+' : escapeRegex(segment)
|
|
29
|
+
)
|
|
30
|
+
.join('\\/');
|
|
31
|
+
}
|