donobu 5.21.5 → 5.21.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/dist/esm/lib/ai/cache/assertCache.d.ts +0 -1
- package/dist/esm/lib/ai/cache/assertCache.js +0 -4
- package/dist/esm/managers/DonobuFlowsManager.d.ts +0 -11
- package/dist/esm/managers/DonobuFlowsManager.js +0 -11
- package/dist/esm/tools/AssertTool.js +15 -11
- package/dist/esm/tools/InputTextTool.d.ts +5 -0
- package/dist/esm/tools/InputTextTool.js +25 -4
- package/dist/lib/ai/cache/assertCache.d.ts +0 -1
- package/dist/lib/ai/cache/assertCache.js +0 -4
- package/dist/managers/DonobuFlowsManager.d.ts +0 -11
- package/dist/managers/DonobuFlowsManager.js +0 -11
- package/dist/tools/AssertTool.js +15 -11
- package/dist/tools/InputTextTool.d.ts +5 -0
- package/dist/tools/InputTextTool.js +25 -4
- package/package.json +1 -1
|
@@ -27,7 +27,6 @@ export declare const PlaywrightAssertionStepSchema: z.ZodObject<{
|
|
|
27
27
|
toHaveURL: "toHaveURL";
|
|
28
28
|
}>;
|
|
29
29
|
attributeValue: z.ZodNullable<z.ZodString>;
|
|
30
|
-
useFirst: z.ZodBoolean;
|
|
31
30
|
}, z.core.$strip>;
|
|
32
31
|
export type PlaywrightAssertionStep = z.infer<typeof PlaywrightAssertionStepSchema>;
|
|
33
32
|
export type AssertCacheExecutor = (context: {
|
|
@@ -81,10 +81,6 @@ Common roles: 'heading', 'button', 'link', 'tab', 'tabpanel', 'dialog', 'navigat
|
|
|
81
81
|
- toHaveAttribute: set to the expected attribute value (e.g. "true" when value="aria-selected")
|
|
82
82
|
- toContainText: set to the text substring to match within the element
|
|
83
83
|
- All other assertions: set to null`),
|
|
84
|
-
/** Whether to narrow to the first match via .first() when the locator might match multiple elements. */
|
|
85
|
-
useFirst: v4_1.z
|
|
86
|
-
.boolean()
|
|
87
|
-
.describe('Set to true to call .first() on the locator when the selector might match multiple elements.'),
|
|
88
84
|
});
|
|
89
85
|
/**
|
|
90
86
|
* Builds an executor function from structured assertion steps.
|
|
@@ -50,17 +50,6 @@ export declare class DonobuFlowsManager {
|
|
|
50
50
|
private readonly flowRuntime;
|
|
51
51
|
private readonly flowCatalog;
|
|
52
52
|
constructor(deploymentEnvironment: DonobuDeploymentEnvironment, gptClientFactory: GptClientFactory, gptConfigsManager: GptConfigsManager, agentsManager: AgentsManager, flowsPersistenceRegistry: FlowsPersistenceRegistry, envDataManager: EnvDataManager, controlPanelFactory: ControlPanelFactory, environ: EnvPick<typeof env, 'ANTHROPIC_API_KEY' | 'ANTHROPIC_MODEL_NAME' | 'AWS_ACCESS_KEY_ID' | 'AWS_BEDROCK_MODEL_NAME' | 'AWS_SECRET_ACCESS_KEY' | 'BASE64_GPT_CONFIG' | 'BROWSERBASE_API_KEY' | 'BROWSERBASE_PROJECT_ID' | 'DONOBU_API_KEY' | 'GOOGLE_GENERATIVE_AI_API_KEY' | 'GOOGLE_GENERATIVE_AI_MODEL_NAME' | 'OLLAMA_API_URL' | 'OLLAMA_MODEL_NAME' | 'OPENAI_API_KEY' | 'OPENAI_API_MODEL_NAME'>, toolRegistry: ToolRegistry, targetRuntimePlugins: TargetRuntimePluginRegistry);
|
|
53
|
-
/**
|
|
54
|
-
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
55
|
-
* method, adding it to list of active flows.
|
|
56
|
-
*
|
|
57
|
-
* @param gptClient If present, will use this as the associated GPT client for
|
|
58
|
-
* this flow instead of instantiating a new one. If so, the
|
|
59
|
-
* gptConfigNameOverride field will be ignored.
|
|
60
|
-
* @param browserContextOverride If present, will use this as the browser
|
|
61
|
-
* context instead of instantiating a new one. If so, most browser
|
|
62
|
-
* related parameters are will be ignored.
|
|
63
|
-
*/
|
|
64
53
|
/**
|
|
65
54
|
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
66
55
|
* method, adding it to list of active flows.
|
|
@@ -87,17 +87,6 @@ class DonobuFlowsManager {
|
|
|
87
87
|
this.flowRuntime = new FlowRuntime_1.FlowRuntime();
|
|
88
88
|
this.flowCatalog = new FlowCatalog_1.FlowCatalog(this.flowsPersistenceRegistry, this.flowRuntime, this.deploymentEnvironment);
|
|
89
89
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
92
|
-
* method, adding it to list of active flows.
|
|
93
|
-
*
|
|
94
|
-
* @param gptClient If present, will use this as the associated GPT client for
|
|
95
|
-
* this flow instead of instantiating a new one. If so, the
|
|
96
|
-
* gptConfigNameOverride field will be ignored.
|
|
97
|
-
* @param browserContextOverride If present, will use this as the browser
|
|
98
|
-
* context instead of instantiating a new one. If so, most browser
|
|
99
|
-
* related parameters are will be ignored.
|
|
100
|
-
*/
|
|
101
90
|
/**
|
|
102
91
|
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
103
92
|
* method, adding it to list of active flows.
|
|
@@ -69,26 +69,30 @@ checking vibes, tone, relying on screenshots, etc) to express as a combination o
|
|
|
69
69
|
|
|
70
70
|
Examples of how assertions map to steps:
|
|
71
71
|
- "Organization Settings heading is visible" →
|
|
72
|
-
[{ locator: "role", role: "heading", value: "Organization Settings", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
72
|
+
[{ locator: "role", role: "heading", value: "Organization Settings", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null }]
|
|
73
73
|
- "Create Template button is visible" →
|
|
74
|
-
[{ locator: "role", role: "button", value: "Create Template", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
74
|
+
[{ locator: "role", role: "button", value: "Create Template", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null }]
|
|
75
75
|
- "pagination shows Page 2 of N" →
|
|
76
|
-
[{ locator: "text", role: null, value: "Page 2 of \\\\d+", valueIsRegex: true, assertion: "toBeVisible", attributeValue: null
|
|
76
|
+
[{ locator: "text", role: null, value: "Page 2 of \\\\d+", valueIsRegex: true, assertion: "toBeVisible", attributeValue: null }]
|
|
77
77
|
- "Programs and Templates tabs are visible" →
|
|
78
|
-
[{ locator: "role", role: "tab", value: "Programs", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
79
|
-
{ locator: "role", role: "tab", value: "Templates", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
78
|
+
[{ locator: "role", role: "tab", value: "Programs", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null },
|
|
79
|
+
{ locator: "role", role: "tab", value: "Templates", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null }]
|
|
80
80
|
- "template X is NOT listed" →
|
|
81
|
-
[{ locator: "text", role: null, value: "X", valueIsRegex: false, assertion: "toBeHidden", attributeValue: null
|
|
81
|
+
[{ locator: "text", role: null, value: "X", valueIsRegex: false, assertion: "toBeHidden", attributeValue: null }]
|
|
82
82
|
- "Next Page is disabled" →
|
|
83
|
-
[{ locator: "role", role: "button", value: "Next Page", valueIsRegex: false, assertion: "toBeDisabled", attributeValue: null
|
|
83
|
+
[{ locator: "role", role: "button", value: "Next Page", valueIsRegex: false, assertion: "toBeDisabled", attributeValue: null }]
|
|
84
84
|
- "The JSON Configuration radio button is selected" →
|
|
85
|
-
[{ locator: "role", role: "radio", value: "JSON Configuration", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null
|
|
85
|
+
[{ locator: "role", role: "radio", value: "JSON Configuration", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null }]
|
|
86
86
|
- "The 'Use installed app' toggle is on" →
|
|
87
|
-
[{ locator: "label", role: null, value: "Use installed app", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null
|
|
87
|
+
[{ locator: "label", role: null, value: "Use installed app", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null }]
|
|
88
88
|
- "The Device Name field contains 'My Device Name'" →
|
|
89
|
-
[{ locator: "label", role: null, value: "Device Name", valueIsRegex: false, assertion: "toHaveValue", attributeValue: "My Device Name"
|
|
89
|
+
[{ locator: "label", role: null, value: "Device Name", valueIsRegex: false, assertion: "toHaveValue", attributeValue: "My Device Name" }]
|
|
90
90
|
- "The Assert Page tool is selected" →
|
|
91
|
-
[{ locator: "role", role: "tab", value: "Assert Page", valueIsRegex: false, assertion: "toHaveAttribute", attributeValue: "true"
|
|
91
|
+
[{ locator: "role", role: "tab", value: "Assert Page", valueIsRegex: false, assertion: "toHaveAttribute", attributeValue: "true" }]
|
|
92
|
+
- "The current URL path is the settings page" →
|
|
93
|
+
[{ locator: "text", role: null, value: ".+/settings/general/?$", valueIsRegex: true, assertion: "toHaveURL", attributeValue: null }]
|
|
94
|
+
- "The current URL path is for a user" →
|
|
95
|
+
[{ locator: "text", role: null, value: ".+/users/[\\d]+/?$", valueIsRegex: true, assertion: "toHaveURL", attributeValue: null }]`),
|
|
92
96
|
});
|
|
93
97
|
class AssertTool extends Tool_1.Tool {
|
|
94
98
|
constructor() {
|
|
@@ -31,6 +31,11 @@ export declare class InputTextTool extends ReplayableInteraction<typeof InputTex
|
|
|
31
31
|
target: ElementHandle<HTMLElement | SVGElement>;
|
|
32
32
|
label?: ElementHandle<HTMLElement | SVGElement>;
|
|
33
33
|
}): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns true when the segment is a single character that Playwright's
|
|
36
|
+
* keyboard.press() can handle: printable ASCII (space through tilde).
|
|
37
|
+
*/
|
|
38
|
+
private static isKeyboardPressable;
|
|
34
39
|
private clearField;
|
|
35
40
|
}
|
|
36
41
|
//# sourceMappingURL=InputTextTool.d.ts.map
|
|
@@ -43,10 +43,24 @@ class InputTextTool extends ReplayableInteraction_1.ReplayableInteraction {
|
|
|
43
43
|
await element.focus();
|
|
44
44
|
const page = (0, TargetUtils_1.webPage)(context);
|
|
45
45
|
await context.interactionVisualizer.pointAt(page, element);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// Segment into grapheme clusters so that composite characters (e.g. ZWJ
|
|
47
|
+
// emoji sequences like 👨👩👧, flags, skin-tone variants) are kept intact.
|
|
48
|
+
const segmenter = new Intl.Segmenter(undefined, {
|
|
49
|
+
granularity: 'grapheme',
|
|
50
|
+
});
|
|
51
|
+
for (const { segment } of segmenter.segment(parameters.text)) {
|
|
52
|
+
if (InputTextTool.isKeyboardPressable(segment)) {
|
|
53
|
+
// Single typeable character — use press() for realistic keydown/keyup.
|
|
54
|
+
await page.keyboard.press(segment, {
|
|
55
|
+
delay: MiscUtils_1.MiscUtils.generateHumanLikeKeyPressDurationInMs(segment),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Multi-code-point cluster or character Playwright can't press (e.g.
|
|
60
|
+
// em-dash, smart quotes, emojis). Insert directly, mimicking a
|
|
61
|
+
// virtual keyboard or input method.
|
|
62
|
+
await page.keyboard.insertText(segment);
|
|
63
|
+
}
|
|
50
64
|
}
|
|
51
65
|
// Submit if requested
|
|
52
66
|
if (parameters.finalizeWithSubmit) {
|
|
@@ -57,6 +71,13 @@ class InputTextTool extends ReplayableInteraction_1.ReplayableInteraction {
|
|
|
57
71
|
}
|
|
58
72
|
return `Inputted text '${parameters.text}' into: `;
|
|
59
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns true when the segment is a single character that Playwright's
|
|
76
|
+
* keyboard.press() can handle: printable ASCII (space through tilde).
|
|
77
|
+
*/
|
|
78
|
+
static isKeyboardPressable(segment) {
|
|
79
|
+
return segment.length === 1 && segment >= ' ' && segment <= '~';
|
|
80
|
+
}
|
|
60
81
|
async clearField(element) {
|
|
61
82
|
try {
|
|
62
83
|
const value = await element.inputValue();
|
|
@@ -27,7 +27,6 @@ export declare const PlaywrightAssertionStepSchema: z.ZodObject<{
|
|
|
27
27
|
toHaveURL: "toHaveURL";
|
|
28
28
|
}>;
|
|
29
29
|
attributeValue: z.ZodNullable<z.ZodString>;
|
|
30
|
-
useFirst: z.ZodBoolean;
|
|
31
30
|
}, z.core.$strip>;
|
|
32
31
|
export type PlaywrightAssertionStep = z.infer<typeof PlaywrightAssertionStepSchema>;
|
|
33
32
|
export type AssertCacheExecutor = (context: {
|
|
@@ -81,10 +81,6 @@ Common roles: 'heading', 'button', 'link', 'tab', 'tabpanel', 'dialog', 'navigat
|
|
|
81
81
|
- toHaveAttribute: set to the expected attribute value (e.g. "true" when value="aria-selected")
|
|
82
82
|
- toContainText: set to the text substring to match within the element
|
|
83
83
|
- All other assertions: set to null`),
|
|
84
|
-
/** Whether to narrow to the first match via .first() when the locator might match multiple elements. */
|
|
85
|
-
useFirst: v4_1.z
|
|
86
|
-
.boolean()
|
|
87
|
-
.describe('Set to true to call .first() on the locator when the selector might match multiple elements.'),
|
|
88
84
|
});
|
|
89
85
|
/**
|
|
90
86
|
* Builds an executor function from structured assertion steps.
|
|
@@ -50,17 +50,6 @@ export declare class DonobuFlowsManager {
|
|
|
50
50
|
private readonly flowRuntime;
|
|
51
51
|
private readonly flowCatalog;
|
|
52
52
|
constructor(deploymentEnvironment: DonobuDeploymentEnvironment, gptClientFactory: GptClientFactory, gptConfigsManager: GptConfigsManager, agentsManager: AgentsManager, flowsPersistenceRegistry: FlowsPersistenceRegistry, envDataManager: EnvDataManager, controlPanelFactory: ControlPanelFactory, environ: EnvPick<typeof env, 'ANTHROPIC_API_KEY' | 'ANTHROPIC_MODEL_NAME' | 'AWS_ACCESS_KEY_ID' | 'AWS_BEDROCK_MODEL_NAME' | 'AWS_SECRET_ACCESS_KEY' | 'BASE64_GPT_CONFIG' | 'BROWSERBASE_API_KEY' | 'BROWSERBASE_PROJECT_ID' | 'DONOBU_API_KEY' | 'GOOGLE_GENERATIVE_AI_API_KEY' | 'GOOGLE_GENERATIVE_AI_MODEL_NAME' | 'OLLAMA_API_URL' | 'OLLAMA_MODEL_NAME' | 'OPENAI_API_KEY' | 'OPENAI_API_MODEL_NAME'>, toolRegistry: ToolRegistry, targetRuntimePlugins: TargetRuntimePluginRegistry);
|
|
53
|
-
/**
|
|
54
|
-
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
55
|
-
* method, adding it to list of active flows.
|
|
56
|
-
*
|
|
57
|
-
* @param gptClient If present, will use this as the associated GPT client for
|
|
58
|
-
* this flow instead of instantiating a new one. If so, the
|
|
59
|
-
* gptConfigNameOverride field will be ignored.
|
|
60
|
-
* @param browserContextOverride If present, will use this as the browser
|
|
61
|
-
* context instead of instantiating a new one. If so, most browser
|
|
62
|
-
* related parameters are will be ignored.
|
|
63
|
-
*/
|
|
64
53
|
/**
|
|
65
54
|
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
66
55
|
* method, adding it to list of active flows.
|
|
@@ -87,17 +87,6 @@ class DonobuFlowsManager {
|
|
|
87
87
|
this.flowRuntime = new FlowRuntime_1.FlowRuntime();
|
|
88
88
|
this.flowCatalog = new FlowCatalog_1.FlowCatalog(this.flowsPersistenceRegistry, this.flowRuntime, this.deploymentEnvironment);
|
|
89
89
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
92
|
-
* method, adding it to list of active flows.
|
|
93
|
-
*
|
|
94
|
-
* @param gptClient If present, will use this as the associated GPT client for
|
|
95
|
-
* this flow instead of instantiating a new one. If so, the
|
|
96
|
-
* gptConfigNameOverride field will be ignored.
|
|
97
|
-
* @param browserContextOverride If present, will use this as the browser
|
|
98
|
-
* context instead of instantiating a new one. If so, most browser
|
|
99
|
-
* related parameters are will be ignored.
|
|
100
|
-
*/
|
|
101
90
|
/**
|
|
102
91
|
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
103
92
|
* method, adding it to list of active flows.
|
package/dist/tools/AssertTool.js
CHANGED
|
@@ -69,26 +69,30 @@ checking vibes, tone, relying on screenshots, etc) to express as a combination o
|
|
|
69
69
|
|
|
70
70
|
Examples of how assertions map to steps:
|
|
71
71
|
- "Organization Settings heading is visible" →
|
|
72
|
-
[{ locator: "role", role: "heading", value: "Organization Settings", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
72
|
+
[{ locator: "role", role: "heading", value: "Organization Settings", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null }]
|
|
73
73
|
- "Create Template button is visible" →
|
|
74
|
-
[{ locator: "role", role: "button", value: "Create Template", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
74
|
+
[{ locator: "role", role: "button", value: "Create Template", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null }]
|
|
75
75
|
- "pagination shows Page 2 of N" →
|
|
76
|
-
[{ locator: "text", role: null, value: "Page 2 of \\\\d+", valueIsRegex: true, assertion: "toBeVisible", attributeValue: null
|
|
76
|
+
[{ locator: "text", role: null, value: "Page 2 of \\\\d+", valueIsRegex: true, assertion: "toBeVisible", attributeValue: null }]
|
|
77
77
|
- "Programs and Templates tabs are visible" →
|
|
78
|
-
[{ locator: "role", role: "tab", value: "Programs", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
79
|
-
{ locator: "role", role: "tab", value: "Templates", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null
|
|
78
|
+
[{ locator: "role", role: "tab", value: "Programs", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null },
|
|
79
|
+
{ locator: "role", role: "tab", value: "Templates", valueIsRegex: false, assertion: "toBeVisible", attributeValue: null }]
|
|
80
80
|
- "template X is NOT listed" →
|
|
81
|
-
[{ locator: "text", role: null, value: "X", valueIsRegex: false, assertion: "toBeHidden", attributeValue: null
|
|
81
|
+
[{ locator: "text", role: null, value: "X", valueIsRegex: false, assertion: "toBeHidden", attributeValue: null }]
|
|
82
82
|
- "Next Page is disabled" →
|
|
83
|
-
[{ locator: "role", role: "button", value: "Next Page", valueIsRegex: false, assertion: "toBeDisabled", attributeValue: null
|
|
83
|
+
[{ locator: "role", role: "button", value: "Next Page", valueIsRegex: false, assertion: "toBeDisabled", attributeValue: null }]
|
|
84
84
|
- "The JSON Configuration radio button is selected" →
|
|
85
|
-
[{ locator: "role", role: "radio", value: "JSON Configuration", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null
|
|
85
|
+
[{ locator: "role", role: "radio", value: "JSON Configuration", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null }]
|
|
86
86
|
- "The 'Use installed app' toggle is on" →
|
|
87
|
-
[{ locator: "label", role: null, value: "Use installed app", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null
|
|
87
|
+
[{ locator: "label", role: null, value: "Use installed app", valueIsRegex: false, assertion: "toBeChecked", attributeValue: null }]
|
|
88
88
|
- "The Device Name field contains 'My Device Name'" →
|
|
89
|
-
[{ locator: "label", role: null, value: "Device Name", valueIsRegex: false, assertion: "toHaveValue", attributeValue: "My Device Name"
|
|
89
|
+
[{ locator: "label", role: null, value: "Device Name", valueIsRegex: false, assertion: "toHaveValue", attributeValue: "My Device Name" }]
|
|
90
90
|
- "The Assert Page tool is selected" →
|
|
91
|
-
[{ locator: "role", role: "tab", value: "Assert Page", valueIsRegex: false, assertion: "toHaveAttribute", attributeValue: "true"
|
|
91
|
+
[{ locator: "role", role: "tab", value: "Assert Page", valueIsRegex: false, assertion: "toHaveAttribute", attributeValue: "true" }]
|
|
92
|
+
- "The current URL path is the settings page" →
|
|
93
|
+
[{ locator: "text", role: null, value: ".+/settings/general/?$", valueIsRegex: true, assertion: "toHaveURL", attributeValue: null }]
|
|
94
|
+
- "The current URL path is for a user" →
|
|
95
|
+
[{ locator: "text", role: null, value: ".+/users/[\\d]+/?$", valueIsRegex: true, assertion: "toHaveURL", attributeValue: null }]`),
|
|
92
96
|
});
|
|
93
97
|
class AssertTool extends Tool_1.Tool {
|
|
94
98
|
constructor() {
|
|
@@ -31,6 +31,11 @@ export declare class InputTextTool extends ReplayableInteraction<typeof InputTex
|
|
|
31
31
|
target: ElementHandle<HTMLElement | SVGElement>;
|
|
32
32
|
label?: ElementHandle<HTMLElement | SVGElement>;
|
|
33
33
|
}): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns true when the segment is a single character that Playwright's
|
|
36
|
+
* keyboard.press() can handle: printable ASCII (space through tilde).
|
|
37
|
+
*/
|
|
38
|
+
private static isKeyboardPressable;
|
|
34
39
|
private clearField;
|
|
35
40
|
}
|
|
36
41
|
//# sourceMappingURL=InputTextTool.d.ts.map
|
|
@@ -43,10 +43,24 @@ class InputTextTool extends ReplayableInteraction_1.ReplayableInteraction {
|
|
|
43
43
|
await element.focus();
|
|
44
44
|
const page = (0, TargetUtils_1.webPage)(context);
|
|
45
45
|
await context.interactionVisualizer.pointAt(page, element);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// Segment into grapheme clusters so that composite characters (e.g. ZWJ
|
|
47
|
+
// emoji sequences like 👨👩👧, flags, skin-tone variants) are kept intact.
|
|
48
|
+
const segmenter = new Intl.Segmenter(undefined, {
|
|
49
|
+
granularity: 'grapheme',
|
|
50
|
+
});
|
|
51
|
+
for (const { segment } of segmenter.segment(parameters.text)) {
|
|
52
|
+
if (InputTextTool.isKeyboardPressable(segment)) {
|
|
53
|
+
// Single typeable character — use press() for realistic keydown/keyup.
|
|
54
|
+
await page.keyboard.press(segment, {
|
|
55
|
+
delay: MiscUtils_1.MiscUtils.generateHumanLikeKeyPressDurationInMs(segment),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Multi-code-point cluster or character Playwright can't press (e.g.
|
|
60
|
+
// em-dash, smart quotes, emojis). Insert directly, mimicking a
|
|
61
|
+
// virtual keyboard or input method.
|
|
62
|
+
await page.keyboard.insertText(segment);
|
|
63
|
+
}
|
|
50
64
|
}
|
|
51
65
|
// Submit if requested
|
|
52
66
|
if (parameters.finalizeWithSubmit) {
|
|
@@ -57,6 +71,13 @@ class InputTextTool extends ReplayableInteraction_1.ReplayableInteraction {
|
|
|
57
71
|
}
|
|
58
72
|
return `Inputted text '${parameters.text}' into: `;
|
|
59
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns true when the segment is a single character that Playwright's
|
|
76
|
+
* keyboard.press() can handle: printable ASCII (space through tilde).
|
|
77
|
+
*/
|
|
78
|
+
static isKeyboardPressable(segment) {
|
|
79
|
+
return segment.length === 1 && segment >= ' ' && segment <= '~';
|
|
80
|
+
}
|
|
60
81
|
async clearField(element) {
|
|
61
82
|
try {
|
|
62
83
|
const value = await element.inputValue();
|