smart-playwright-nlp 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 ADDED
@@ -0,0 +1,46 @@
1
+ # smart-playwright-nlp 🚀
2
+
3
+ An AI-assisted natural language and text-based locator wrapper for **Playwright**. Author end-to-end tests using intuitive, human-readable instructions backed by a robust, multi-tier locator fallback engine.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/smart-playwright-nlp.svg)](https://www.npmjs.com/package/smart-playwright-nlp)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## 🌟 Key Features
11
+
12
+ * 📝 **Natural Language Parsing:** Powered by lightweight deterministic tokenization (`compromise` NLP), eliminating heavy API latency.
13
+ * 🛡️ **Multi-Tier Smart Locators:** Cascades through accessibility roles, data attributes, placeholders, labels, and text-proximity rules to avoid test flakiness.
14
+ * 📦 **Dual Module Export:** Ships fully compiled with clean **ESM** (`.mjs`) and **CommonJS** (`.js`) paths with baked-in TypeScript declaration maps (`.d.ts`).
15
+ * ⚡ **Zero-Config Playwright Integration:** Drop it straight into your current standard `@playwright/test` runner configuration hooks.
16
+
17
+ ---
18
+
19
+ ## 💾 Installation
20
+
21
+ Ensure you have `@playwright/test` installed in your host project repository:
22
+
23
+ ```bash
24
+ npm install smart-playwright-nlp
25
+
26
+ Usage
27
+
28
+ import { test } from '@playwright/test';
29
+ import { SmartWrapper } from 'smart-playwright-nlp';
30
+
31
+ test('End-to-End Core E-Commerce Purchase Flow', async ({ page }) => {
32
+ const ai = new SmartWrapper(page);
33
+
34
+ // 1. Navigation Commands
35
+ await ai.execute("Navigate to '[https://example.com/login](https://example.com/login)'");
36
+
37
+ // 2. Interactive Form Actions (First quotes = value, Second quotes = target)
38
+ await ai.execute("Type 'test_admin' into 'Username'");
39
+ await ai.execute("Type 'SecurePassword123' into 'Password'");
40
+ await ai.execute("Click the 'Sign In' button");
41
+
42
+ // 3. Smart Web-First Assertions
43
+ await ai.execute("Verify that the text 'Welcome Back' is visible");
44
+ await ai.execute("Verify that url contains '/dashboard'");
45
+ });
46
+
@@ -0,0 +1,18 @@
1
+ import { Page } from '@playwright/test';
2
+
3
+ declare class SmartWrapper {
4
+ private page;
5
+ constructor(page: Page);
6
+ /**
7
+ * Main entry point to process any natural language string
8
+ */
9
+ execute(instruction: string): Promise<void>;
10
+ private isNavigation;
11
+ private handleNavigation;
12
+ private handleAction;
13
+ private isAssertion;
14
+ private handleAssertion;
15
+ private resolveSmartLocator;
16
+ }
17
+
18
+ export { SmartWrapper };
@@ -0,0 +1,18 @@
1
+ import { Page } from '@playwright/test';
2
+
3
+ declare class SmartWrapper {
4
+ private page;
5
+ constructor(page: Page);
6
+ /**
7
+ * Main entry point to process any natural language string
8
+ */
9
+ execute(instruction: string): Promise<void>;
10
+ private isNavigation;
11
+ private handleNavigation;
12
+ private handleAction;
13
+ private isAssertion;
14
+ private handleAssertion;
15
+ private resolveSmartLocator;
16
+ }
17
+
18
+ export { SmartWrapper };
package/dist/index.js ADDED
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ SmartWrapper: () => SmartWrapper
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_test = require("@playwright/test");
37
+
38
+ // src/parser/intentParser.ts
39
+ var import_compromise = __toESM(require("compromise"));
40
+ function parseInstruction(text) {
41
+ const normalized = text.trim();
42
+ const doc = (0, import_compromise.default)(normalized.toLowerCase());
43
+ const quotes = normalized.match(/['"‘“](.*?)['"”’]/g)?.map((q) => q.replace(/['"‘“]/g, "")) || [];
44
+ if (doc.has("navigate to") || doc.has("goto") || doc.has("open")) {
45
+ return { action: "goto", target: quotes[0] || normalized.replace(/navigate to|goto|open/gi, "").trim() };
46
+ }
47
+ if (doc.has("reload") || doc.has("refresh")) {
48
+ return { action: "reload", target: "" };
49
+ }
50
+ if (doc.has("go back")) {
51
+ return { action: "back", target: "" };
52
+ }
53
+ if (doc.has("go forward")) {
54
+ return { action: "forward", target: "" };
55
+ }
56
+ if (doc.has("wait for network") || doc.has("wait until idle")) {
57
+ return { action: "wait_network", target: "" };
58
+ }
59
+ if (doc.has("verify") || doc.has("expect") || doc.has("assert") || doc.has("should be")) {
60
+ if (doc.has("url")) {
61
+ return { action: "assert_url", target: "url", value: quotes[0] };
62
+ }
63
+ if (doc.has("visible") || doc.has("see")) {
64
+ return { action: "assert_visible", target: quotes[0] || normalized, role: inferRole(normalized) };
65
+ }
66
+ if (doc.has("hidden") || doc.has("disappear")) {
67
+ return { action: "assert_hidden", target: quotes[0] || normalized, role: inferRole(normalized) };
68
+ }
69
+ if (doc.has("enabled")) {
70
+ return { action: "assert_enabled", target: quotes[0] || normalized, role: inferRole(normalized) };
71
+ }
72
+ if (doc.has("checked")) {
73
+ return { action: "assert_checked", target: quotes[0] || normalized, role: inferRole(normalized) };
74
+ }
75
+ if (doc.has("value")) {
76
+ return { action: "assert_value", target: quotes[1] || "", value: quotes[0] };
77
+ }
78
+ return { action: "assert_text", target: quotes[1] || normalized, value: quotes[0] };
79
+ }
80
+ if (doc.has("double click") || doc.has("dblclick")) {
81
+ return { action: "dblclick", target: quotes[0] || normalized, role: inferRole(normalized) };
82
+ }
83
+ if (doc.has("click") || doc.has("tap") || doc.has("press")) {
84
+ if (doc.has("key") || doc.has("enter key") || doc.has("escape")) {
85
+ return { action: "press_key", target: quotes[1] || "body", value: quotes[0] || "Enter" };
86
+ }
87
+ return { action: "click", target: quotes[0] || normalized, role: inferRole(normalized) };
88
+ }
89
+ if (doc.has("hover") || doc.has("mouse over")) {
90
+ return { action: "hover", target: quotes[0] || normalized, role: inferRole(normalized) };
91
+ }
92
+ if (doc.has("clear") || doc.has("wipe")) {
93
+ return { action: "clear", target: quotes[0] || normalized, role: "textbox" };
94
+ }
95
+ if (doc.has("uncheck")) {
96
+ return { action: "uncheck", target: quotes[0] || normalized, role: "checkbox" };
97
+ }
98
+ if (doc.has("check")) {
99
+ return { action: "check", target: quotes[0] || normalized, role: "checkbox" };
100
+ }
101
+ if (doc.has("type") || doc.has("fill") || doc.has("enter")) {
102
+ return {
103
+ action: "fill",
104
+ value: quotes[0],
105
+ // What to type (First quoted string)
106
+ target: quotes[1] || normalized,
107
+ // Where to type it (Second quoted string)
108
+ role: "textbox"
109
+ };
110
+ }
111
+ if (doc.has("select") || doc.has("choose")) {
112
+ return {
113
+ action: "select",
114
+ value: quotes[0],
115
+ // Option text/value
116
+ target: quotes[1] || normalized,
117
+ // Dropdown target element
118
+ role: "combobox"
119
+ };
120
+ }
121
+ if (doc.has("upload") || doc.has("attach file")) {
122
+ return {
123
+ action: "upload",
124
+ value: quotes[0],
125
+ // System file path reference location
126
+ target: quotes[1] || normalized
127
+ };
128
+ }
129
+ return { action: "unknown", target: normalized };
130
+ }
131
+ function inferRole(text) {
132
+ const lower = text.toLowerCase();
133
+ if (lower.includes("button")) return "button";
134
+ if (lower.includes("input") || lower.includes("field") || lower.includes("box")) return "textbox";
135
+ if (lower.includes("checkbox")) return "checkbox";
136
+ if (lower.includes("dropdown") || lower.includes("select")) return "combobox";
137
+ if (lower.includes("link")) return "link";
138
+ if (lower.includes("heading")) return "heading";
139
+ return void 0;
140
+ }
141
+
142
+ // src/index.ts
143
+ var SmartWrapper = class {
144
+ page;
145
+ constructor(page) {
146
+ this.page = page;
147
+ }
148
+ /**
149
+ * Main entry point to process any natural language string
150
+ */
151
+ async execute(instruction) {
152
+ const parsed = parseInstruction(instruction);
153
+ if (parsed.action === "unknown") {
154
+ throw new Error(`Unable to cleanly parse step intent: "${instruction}"`);
155
+ }
156
+ if (this.isNavigation(parsed.action)) {
157
+ await this.handleNavigation(parsed);
158
+ } else if (this.isAssertion(parsed.action)) {
159
+ await this.handleAssertion(parsed);
160
+ } else {
161
+ await this.handleAction(parsed);
162
+ }
163
+ }
164
+ // =========================================================================
165
+ // 1. Navigation & Context Command Processor
166
+ // =========================================================================
167
+ isNavigation(action) {
168
+ return ["goto", "reload", "back", "forward", "wait_network"].includes(action);
169
+ }
170
+ async handleNavigation(command) {
171
+ switch (command.action) {
172
+ case "goto":
173
+ await this.page.goto(command.target);
174
+ break;
175
+ case "reload":
176
+ await this.page.reload();
177
+ break;
178
+ case "back":
179
+ await this.page.goBack();
180
+ break;
181
+ case "forward":
182
+ await this.page.goForward();
183
+ break;
184
+ case "wait_network":
185
+ await this.page.waitForLoadState("networkidle");
186
+ break;
187
+ }
188
+ }
189
+ // =========================================================================
190
+ // 2. Interaction Actions Processor
191
+ // =========================================================================
192
+ async handleAction(command) {
193
+ const element = await this.resolveSmartLocator(command.target, command.role);
194
+ switch (command.action) {
195
+ case "click":
196
+ await element.click();
197
+ break;
198
+ case "dblclick":
199
+ await element.dblclick();
200
+ break;
201
+ case "hover":
202
+ await element.hover();
203
+ break;
204
+ case "clear":
205
+ await element.clear();
206
+ break;
207
+ case "check":
208
+ await element.check();
209
+ break;
210
+ case "uncheck":
211
+ await element.uncheck();
212
+ break;
213
+ case "type":
214
+ case "fill":
215
+ if (command.value === void 0) throw new Error(`Type action requires a trailing value mapping rule for target "${command.target}"`);
216
+ await element.fill(command.value);
217
+ break;
218
+ case "press_key":
219
+ if (command.value === void 0) throw new Error(`Press key requires a mapped trigger shortcut key`);
220
+ await element.press(command.value);
221
+ break;
222
+ case "select":
223
+ if (command.value === void 0) throw new Error(`Select target options require a target value option flag`);
224
+ await element.selectOption(command.value);
225
+ break;
226
+ case "upload":
227
+ if (command.value === void 0) throw new Error(`Upload operations require valid absolute file references`);
228
+ await element.setInputFiles(command.value.split(","));
229
+ break;
230
+ }
231
+ }
232
+ // =========================================================================
233
+ // 3. Web-First Assertions Processor
234
+ // =========================================================================
235
+ isAssertion(action) {
236
+ return ["assert_visible", "assert_hidden", "assert_enabled", "assert_checked", "assert_text", "assert_value", "assert_url"].includes(action);
237
+ }
238
+ async handleAssertion(command) {
239
+ if (command.action === "assert_url") {
240
+ if (!command.value) throw new Error("URL confirmation assertions require a baseline comparison string reference");
241
+ await (0, import_test.expect)(this.page).toHaveURL(command.value);
242
+ return;
243
+ }
244
+ const element = await this.resolveSmartLocator(command.target, command.role);
245
+ switch (command.action) {
246
+ case "assert_visible":
247
+ await (0, import_test.expect)(element).toBeVisible();
248
+ break;
249
+ case "assert_hidden":
250
+ await (0, import_test.expect)(element).toBeHidden();
251
+ break;
252
+ case "assert_enabled":
253
+ await (0, import_test.expect)(element).toBeEnabled();
254
+ break;
255
+ case "assert_checked":
256
+ await (0, import_test.expect)(element).toBeChecked();
257
+ break;
258
+ case "assert_text":
259
+ if (!command.value) throw new Error("Text verification requires validation targets");
260
+ await (0, import_test.expect)(element).toContainText(command.value);
261
+ break;
262
+ case "assert_value":
263
+ if (!command.value) throw new Error("Value validation matches require target data strings");
264
+ await (0, import_test.expect)(element).toHaveValue(command.value);
265
+ break;
266
+ }
267
+ }
268
+ // =========================================================================
269
+ // 4. Hierarchical Locator Engine (Fallback System)
270
+ // =========================================================================
271
+ async resolveSmartLocator(target, explicitRole) {
272
+ if (explicitRole) {
273
+ try {
274
+ return this.page.getByRole(explicitRole, { name: target, exact: false }).first();
275
+ } catch {
276
+ }
277
+ }
278
+ try {
279
+ const testIdLocator = this.page.getByTestId(target);
280
+ if (await testIdLocator.count() > 0) return testIdLocator.first();
281
+ } catch {
282
+ }
283
+ try {
284
+ const placeholderLocator = this.page.getByPlaceholder(target, { exact: false });
285
+ if (await placeholderLocator.count() > 0) return placeholderLocator.first();
286
+ } catch {
287
+ }
288
+ try {
289
+ const labelLocator = this.page.getByLabel(target, { exact: false });
290
+ if (await labelLocator.count() > 0) return labelLocator.first();
291
+ } catch {
292
+ }
293
+ try {
294
+ const textLocator = this.page.getByText(target, { exact: false });
295
+ if (await textLocator.count() > 0) return textLocator.first();
296
+ } catch {
297
+ }
298
+ try {
299
+ const nearInputLocator = this.page.locator(`input:near(:text("${target}"))`);
300
+ if (await nearInputLocator.count() > 0) return nearInputLocator.first();
301
+ } catch {
302
+ }
303
+ return this.page.locator(target).first();
304
+ }
305
+ };
306
+ // Annotate the CommonJS export names for ESM import in node:
307
+ 0 && (module.exports = {
308
+ SmartWrapper
309
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,274 @@
1
+ // src/index.ts
2
+ import { expect } from "@playwright/test";
3
+
4
+ // src/parser/intentParser.ts
5
+ import nlp from "compromise";
6
+ function parseInstruction(text) {
7
+ const normalized = text.trim();
8
+ const doc = nlp(normalized.toLowerCase());
9
+ const quotes = normalized.match(/['"‘“](.*?)['"”’]/g)?.map((q) => q.replace(/['"‘“]/g, "")) || [];
10
+ if (doc.has("navigate to") || doc.has("goto") || doc.has("open")) {
11
+ return { action: "goto", target: quotes[0] || normalized.replace(/navigate to|goto|open/gi, "").trim() };
12
+ }
13
+ if (doc.has("reload") || doc.has("refresh")) {
14
+ return { action: "reload", target: "" };
15
+ }
16
+ if (doc.has("go back")) {
17
+ return { action: "back", target: "" };
18
+ }
19
+ if (doc.has("go forward")) {
20
+ return { action: "forward", target: "" };
21
+ }
22
+ if (doc.has("wait for network") || doc.has("wait until idle")) {
23
+ return { action: "wait_network", target: "" };
24
+ }
25
+ if (doc.has("verify") || doc.has("expect") || doc.has("assert") || doc.has("should be")) {
26
+ if (doc.has("url")) {
27
+ return { action: "assert_url", target: "url", value: quotes[0] };
28
+ }
29
+ if (doc.has("visible") || doc.has("see")) {
30
+ return { action: "assert_visible", target: quotes[0] || normalized, role: inferRole(normalized) };
31
+ }
32
+ if (doc.has("hidden") || doc.has("disappear")) {
33
+ return { action: "assert_hidden", target: quotes[0] || normalized, role: inferRole(normalized) };
34
+ }
35
+ if (doc.has("enabled")) {
36
+ return { action: "assert_enabled", target: quotes[0] || normalized, role: inferRole(normalized) };
37
+ }
38
+ if (doc.has("checked")) {
39
+ return { action: "assert_checked", target: quotes[0] || normalized, role: inferRole(normalized) };
40
+ }
41
+ if (doc.has("value")) {
42
+ return { action: "assert_value", target: quotes[1] || "", value: quotes[0] };
43
+ }
44
+ return { action: "assert_text", target: quotes[1] || normalized, value: quotes[0] };
45
+ }
46
+ if (doc.has("double click") || doc.has("dblclick")) {
47
+ return { action: "dblclick", target: quotes[0] || normalized, role: inferRole(normalized) };
48
+ }
49
+ if (doc.has("click") || doc.has("tap") || doc.has("press")) {
50
+ if (doc.has("key") || doc.has("enter key") || doc.has("escape")) {
51
+ return { action: "press_key", target: quotes[1] || "body", value: quotes[0] || "Enter" };
52
+ }
53
+ return { action: "click", target: quotes[0] || normalized, role: inferRole(normalized) };
54
+ }
55
+ if (doc.has("hover") || doc.has("mouse over")) {
56
+ return { action: "hover", target: quotes[0] || normalized, role: inferRole(normalized) };
57
+ }
58
+ if (doc.has("clear") || doc.has("wipe")) {
59
+ return { action: "clear", target: quotes[0] || normalized, role: "textbox" };
60
+ }
61
+ if (doc.has("uncheck")) {
62
+ return { action: "uncheck", target: quotes[0] || normalized, role: "checkbox" };
63
+ }
64
+ if (doc.has("check")) {
65
+ return { action: "check", target: quotes[0] || normalized, role: "checkbox" };
66
+ }
67
+ if (doc.has("type") || doc.has("fill") || doc.has("enter")) {
68
+ return {
69
+ action: "fill",
70
+ value: quotes[0],
71
+ // What to type (First quoted string)
72
+ target: quotes[1] || normalized,
73
+ // Where to type it (Second quoted string)
74
+ role: "textbox"
75
+ };
76
+ }
77
+ if (doc.has("select") || doc.has("choose")) {
78
+ return {
79
+ action: "select",
80
+ value: quotes[0],
81
+ // Option text/value
82
+ target: quotes[1] || normalized,
83
+ // Dropdown target element
84
+ role: "combobox"
85
+ };
86
+ }
87
+ if (doc.has("upload") || doc.has("attach file")) {
88
+ return {
89
+ action: "upload",
90
+ value: quotes[0],
91
+ // System file path reference location
92
+ target: quotes[1] || normalized
93
+ };
94
+ }
95
+ return { action: "unknown", target: normalized };
96
+ }
97
+ function inferRole(text) {
98
+ const lower = text.toLowerCase();
99
+ if (lower.includes("button")) return "button";
100
+ if (lower.includes("input") || lower.includes("field") || lower.includes("box")) return "textbox";
101
+ if (lower.includes("checkbox")) return "checkbox";
102
+ if (lower.includes("dropdown") || lower.includes("select")) return "combobox";
103
+ if (lower.includes("link")) return "link";
104
+ if (lower.includes("heading")) return "heading";
105
+ return void 0;
106
+ }
107
+
108
+ // src/index.ts
109
+ var SmartWrapper = class {
110
+ page;
111
+ constructor(page) {
112
+ this.page = page;
113
+ }
114
+ /**
115
+ * Main entry point to process any natural language string
116
+ */
117
+ async execute(instruction) {
118
+ const parsed = parseInstruction(instruction);
119
+ if (parsed.action === "unknown") {
120
+ throw new Error(`Unable to cleanly parse step intent: "${instruction}"`);
121
+ }
122
+ if (this.isNavigation(parsed.action)) {
123
+ await this.handleNavigation(parsed);
124
+ } else if (this.isAssertion(parsed.action)) {
125
+ await this.handleAssertion(parsed);
126
+ } else {
127
+ await this.handleAction(parsed);
128
+ }
129
+ }
130
+ // =========================================================================
131
+ // 1. Navigation & Context Command Processor
132
+ // =========================================================================
133
+ isNavigation(action) {
134
+ return ["goto", "reload", "back", "forward", "wait_network"].includes(action);
135
+ }
136
+ async handleNavigation(command) {
137
+ switch (command.action) {
138
+ case "goto":
139
+ await this.page.goto(command.target);
140
+ break;
141
+ case "reload":
142
+ await this.page.reload();
143
+ break;
144
+ case "back":
145
+ await this.page.goBack();
146
+ break;
147
+ case "forward":
148
+ await this.page.goForward();
149
+ break;
150
+ case "wait_network":
151
+ await this.page.waitForLoadState("networkidle");
152
+ break;
153
+ }
154
+ }
155
+ // =========================================================================
156
+ // 2. Interaction Actions Processor
157
+ // =========================================================================
158
+ async handleAction(command) {
159
+ const element = await this.resolveSmartLocator(command.target, command.role);
160
+ switch (command.action) {
161
+ case "click":
162
+ await element.click();
163
+ break;
164
+ case "dblclick":
165
+ await element.dblclick();
166
+ break;
167
+ case "hover":
168
+ await element.hover();
169
+ break;
170
+ case "clear":
171
+ await element.clear();
172
+ break;
173
+ case "check":
174
+ await element.check();
175
+ break;
176
+ case "uncheck":
177
+ await element.uncheck();
178
+ break;
179
+ case "type":
180
+ case "fill":
181
+ if (command.value === void 0) throw new Error(`Type action requires a trailing value mapping rule for target "${command.target}"`);
182
+ await element.fill(command.value);
183
+ break;
184
+ case "press_key":
185
+ if (command.value === void 0) throw new Error(`Press key requires a mapped trigger shortcut key`);
186
+ await element.press(command.value);
187
+ break;
188
+ case "select":
189
+ if (command.value === void 0) throw new Error(`Select target options require a target value option flag`);
190
+ await element.selectOption(command.value);
191
+ break;
192
+ case "upload":
193
+ if (command.value === void 0) throw new Error(`Upload operations require valid absolute file references`);
194
+ await element.setInputFiles(command.value.split(","));
195
+ break;
196
+ }
197
+ }
198
+ // =========================================================================
199
+ // 3. Web-First Assertions Processor
200
+ // =========================================================================
201
+ isAssertion(action) {
202
+ return ["assert_visible", "assert_hidden", "assert_enabled", "assert_checked", "assert_text", "assert_value", "assert_url"].includes(action);
203
+ }
204
+ async handleAssertion(command) {
205
+ if (command.action === "assert_url") {
206
+ if (!command.value) throw new Error("URL confirmation assertions require a baseline comparison string reference");
207
+ await expect(this.page).toHaveURL(command.value);
208
+ return;
209
+ }
210
+ const element = await this.resolveSmartLocator(command.target, command.role);
211
+ switch (command.action) {
212
+ case "assert_visible":
213
+ await expect(element).toBeVisible();
214
+ break;
215
+ case "assert_hidden":
216
+ await expect(element).toBeHidden();
217
+ break;
218
+ case "assert_enabled":
219
+ await expect(element).toBeEnabled();
220
+ break;
221
+ case "assert_checked":
222
+ await expect(element).toBeChecked();
223
+ break;
224
+ case "assert_text":
225
+ if (!command.value) throw new Error("Text verification requires validation targets");
226
+ await expect(element).toContainText(command.value);
227
+ break;
228
+ case "assert_value":
229
+ if (!command.value) throw new Error("Value validation matches require target data strings");
230
+ await expect(element).toHaveValue(command.value);
231
+ break;
232
+ }
233
+ }
234
+ // =========================================================================
235
+ // 4. Hierarchical Locator Engine (Fallback System)
236
+ // =========================================================================
237
+ async resolveSmartLocator(target, explicitRole) {
238
+ if (explicitRole) {
239
+ try {
240
+ return this.page.getByRole(explicitRole, { name: target, exact: false }).first();
241
+ } catch {
242
+ }
243
+ }
244
+ try {
245
+ const testIdLocator = this.page.getByTestId(target);
246
+ if (await testIdLocator.count() > 0) return testIdLocator.first();
247
+ } catch {
248
+ }
249
+ try {
250
+ const placeholderLocator = this.page.getByPlaceholder(target, { exact: false });
251
+ if (await placeholderLocator.count() > 0) return placeholderLocator.first();
252
+ } catch {
253
+ }
254
+ try {
255
+ const labelLocator = this.page.getByLabel(target, { exact: false });
256
+ if (await labelLocator.count() > 0) return labelLocator.first();
257
+ } catch {
258
+ }
259
+ try {
260
+ const textLocator = this.page.getByText(target, { exact: false });
261
+ if (await textLocator.count() > 0) return textLocator.first();
262
+ } catch {
263
+ }
264
+ try {
265
+ const nearInputLocator = this.page.locator(`input:near(:text("${target}"))`);
266
+ if (await nearInputLocator.count() > 0) return nearInputLocator.first();
267
+ } catch {
268
+ }
269
+ return this.page.locator(target).first();
270
+ }
271
+ };
272
+ export {
273
+ SmartWrapper
274
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "smart-playwright-nlp",
3
+ "version": "1.0.0",
4
+ "description": "Natural language and text-based locator wrapper for Playwright",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
19
+ "prepublishOnly": "npm run build",
20
+ "test": "echo \"Error: no test specified\" && exit 1"
21
+ },
22
+ "peerDependencies": {
23
+ "@playwright/test": ">=1.0.0"
24
+ },
25
+ "dependencies": {
26
+ "compromise": "^14.15.1"
27
+ },
28
+ "devDependencies": {
29
+ "@playwright/test": "^1.61.0",
30
+ "tsup": "^8.5.1",
31
+ "typescript": "^6.0.3"
32
+ },
33
+ "keywords": [
34
+ "playwright",
35
+ "nlp",
36
+ "qa",
37
+ "test-automation",
38
+ "smart-locators"
39
+ ],
40
+ "author": "",
41
+ "license": "MIT"
42
+ }