Skip to content

Commit f03164c

Browse files
test: add CDP method tests (#1141)
#### 1. **frame-get-location-and-click.spec.ts** (1 test) - **Coordinate-based clicking using CDP DOM queries** - Tests `Frame.getLocationForSelector()` to get element bounding box - Tests `Page.click()` coordinate-based clicking via CDP Input events - Verifies element state changes after CDP-driven clicks #### 2. **locator-input-methods.spec.ts** (7 tests) - **Locator.fill()** - Direct DOM value setting on inputs - **Locator.type()** - Character-by-character text input via keyboard events - **Locator.hover()** - Mouse movement to element center - **Locator.isVisible()** - Visibility detection (5 edge cases: display:none, visibility:hidden, opacity:0, zero-size, connected nodes) - **Locator.isChecked()** - Checkbox and radio button state detection - **fill() on textarea** - Multi-line text input - **fill() replacement** - Clearing and replacing existing values #### 3. **locator-content-methods.spec.ts** (10 tests) - **Locator.textContent()** - Raw text extraction (includes hidden content) - **Locator.innerText()** - Layout-aware visible text extraction - **Locator.innerHtml()** - HTML markup extraction with attributes - **Locator.inputValue()** - Value extraction from inputs, textareas, contenteditable - **Edge cases covered:** Empty elements, nested formatting, script/style tags, contenteditable, non-input elements #### 4. **locator-select-option.spec.ts** (10 tests) - **Single & multiple option selection** - By value and label - **Option deselection** - Auto-deselection on single select - **Change event firing** - Properly triggered on selection - **Optgroup structure** - Grouped options support - **Edge cases:** Empty values, numeric strings, disabled options, multi-select arrays --------- Co-authored-by: Sean McGuire <[email protected]>
1 parent 7f36683 commit f03164c

File tree

8 files changed

+1818
-1
lines changed

8 files changed

+1818
-1
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ packages/core/lib/version.ts
2323
packages/core/test-results/
2424
/examples/inference_summary
2525
/inference_summary
26-
.turbo
26+
.turbo
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect, test } from "@playwright/test";
2+
import { V3 } from "../v3";
3+
import { v3TestConfig } from "./v3.config";
4+
5+
test.describe("Coordinate-based clicking", () => {
6+
let v3: V3;
7+
8+
test.beforeEach(async () => {
9+
v3 = new V3(v3TestConfig);
10+
await v3.init();
11+
});
12+
13+
test.afterEach(async () => {
14+
await v3?.close?.().catch(() => {});
15+
});
16+
17+
test("clicking by coordinates toggles a button state", async () => {
18+
const page = v3.context.pages()[0];
19+
20+
await page.goto(
21+
"data:text/html," +
22+
encodeURIComponent(
23+
`<!doctype html><html><body>
24+
<button id="btn" onclick="this.dataset.clicked = (this.dataset.clicked==='1'?'0':'1')">Click</button>
25+
<div id="out"></div>
26+
<script>
27+
const btn = document.getElementById('btn');
28+
const out = document.getElementById('out');
29+
const update = () => { out.textContent = btn.dataset.clicked === '1' ? 'clicked' : 'idle'; };
30+
update();
31+
btn.addEventListener('click', update);
32+
</script>
33+
</body></html>`,
34+
),
35+
);
36+
37+
// Initial state should be idle
38+
let state = await page.mainFrame().evaluate(() => {
39+
const out = document.getElementById("out");
40+
return out?.textContent || "";
41+
});
42+
expect(state).toBe("idle");
43+
44+
// Compute button location via Frame.getLocationForSelector
45+
const { x, y, width, height } = await page
46+
.mainFrame()
47+
.getLocationForSelector("#btn");
48+
49+
// Click near the center of the button using Page.click coordinates
50+
const cx = Math.round(x + width / 2);
51+
const cy = Math.round(y + height / 2);
52+
await page.click(cx, cy);
53+
54+
state = await page.mainFrame().evaluate(() => {
55+
const out = document.getElementById("out");
56+
return out?.textContent || "";
57+
});
58+
expect(state).toBe("clicked");
59+
60+
// Click again to toggle back to idle
61+
await page.click(cx, cy);
62+
state = await page.mainFrame().evaluate(() => {
63+
const out = document.getElementById("out");
64+
return out?.textContent || "";
65+
});
66+
expect(state).toBe("idle");
67+
});
68+
});
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import { test, expect } from "@playwright/test";
2+
import { V3 } from "../v3";
3+
import { v3TestConfig } from "./v3.config";
4+
5+
test.describe("Locator.backendNodeId() - CDP DOM node ID", () => {
6+
let v3: V3;
7+
8+
test.beforeEach(async () => {
9+
v3 = new V3(v3TestConfig);
10+
await v3.init();
11+
});
12+
13+
test.afterEach(async () => {
14+
await v3?.close?.().catch(() => {});
15+
});
16+
17+
test("returns a valid backend node ID for an element", async () => {
18+
const page = v3.context.pages()[0];
19+
20+
await page.goto(
21+
"data:text/html," +
22+
encodeURIComponent(
23+
`<!doctype html><html><body>
24+
<button id="btn">Click me</button>
25+
</body></html>`,
26+
),
27+
);
28+
29+
const locator = page.locator("button#btn");
30+
const nodeId = await locator.backendNodeId();
31+
32+
// Backend node ID should be a valid number
33+
expect(typeof nodeId).toBe("number");
34+
expect(nodeId).toBeGreaterThan(0);
35+
});
36+
37+
test("returns different node IDs for different elements", async () => {
38+
const page = v3.context.pages()[0];
39+
40+
await page.goto(
41+
"data:text/html," +
42+
encodeURIComponent(
43+
`<!doctype html><html><body>
44+
<div id="div1">First</div>
45+
<div id="div2">Second</div>
46+
<p id="p1">Third</p>
47+
</body></html>`,
48+
),
49+
);
50+
51+
const nodeId1 = await page.locator("div#div1").backendNodeId();
52+
const nodeId2 = await page.locator("div#div2").backendNodeId();
53+
const nodeId3 = await page.locator("p#p1").backendNodeId();
54+
55+
// All node IDs should be unique
56+
expect(nodeId1).not.toBe(nodeId2);
57+
expect(nodeId2).not.toBe(nodeId3);
58+
expect(nodeId1).not.toBe(nodeId3);
59+
});
60+
61+
test("returns consistent node ID for the same element", async () => {
62+
const page = v3.context.pages()[0];
63+
64+
await page.goto(
65+
"data:text/html," +
66+
encodeURIComponent(
67+
`<!doctype html><html><body>
68+
<input type="text" id="input" />
69+
</body></html>`,
70+
),
71+
);
72+
73+
const locator = page.locator("input#input");
74+
75+
// Call multiple times on the same element
76+
const nodeId1 = await locator.backendNodeId();
77+
const nodeId2 = await locator.backendNodeId();
78+
79+
// Should return the same ID (same element)
80+
expect(nodeId1).toBe(nodeId2);
81+
});
82+
83+
test("returns node ID for nested elements", async () => {
84+
const page = v3.context.pages()[0];
85+
86+
await page.goto(
87+
"data:text/html," +
88+
encodeURIComponent(
89+
`<!doctype html><html><body>
90+
<div id="outer">
91+
<div id="middle">
92+
<span id="inner">Deep</span>
93+
</div>
94+
</div>
95+
</body></html>`,
96+
),
97+
);
98+
99+
const outerNodeId = await page.locator("div#outer").backendNodeId();
100+
const middleNodeId = await page.locator("div#middle").backendNodeId();
101+
const innerNodeId = await page.locator("span#inner").backendNodeId();
102+
103+
// All should be valid and unique
104+
expect(outerNodeId).toBeGreaterThan(0);
105+
expect(middleNodeId).toBeGreaterThan(0);
106+
expect(innerNodeId).toBeGreaterThan(0);
107+
expect(new Set([outerNodeId, middleNodeId, innerNodeId]).size).toBe(3);
108+
});
109+
110+
test("returns node ID for elements with various attributes", async () => {
111+
const page = v3.context.pages()[0];
112+
113+
await page.goto(
114+
"data:text/html," +
115+
encodeURIComponent(
116+
`<!doctype html><html><body>
117+
<button class="btn primary" data-test="submit" aria-label="Submit form">Save</button>
118+
</body></html>`,
119+
),
120+
);
121+
122+
const locator = page.locator("button");
123+
const nodeId = await locator.backendNodeId();
124+
125+
// Should work with complex elements
126+
expect(typeof nodeId).toBe("number");
127+
expect(nodeId).toBeGreaterThan(0);
128+
});
129+
130+
test("returns node ID for form elements", async () => {
131+
const page = v3.context.pages()[0];
132+
133+
await page.goto(
134+
"data:text/html," +
135+
encodeURIComponent(
136+
`<!doctype html><html><body>
137+
<form>
138+
<input type="email" id="email" placeholder="Email" />
139+
<textarea id="message"></textarea>
140+
<select id="country">
141+
<option value="us">USA</option>
142+
<option value="ca">Canada</option>
143+
</select>
144+
<button type="submit">Submit</button>
145+
</form>
146+
</body></html>`,
147+
),
148+
);
149+
150+
const emailNodeId = await page.locator("input#email").backendNodeId();
151+
const textareaNodeId = await page
152+
.locator("textarea#message")
153+
.backendNodeId();
154+
const selectNodeId = await page.locator("select#country").backendNodeId();
155+
const submitNodeId = await page
156+
.locator("button[type='submit']")
157+
.backendNodeId();
158+
159+
// All form elements should have valid node IDs
160+
expect(emailNodeId).toBeGreaterThan(0);
161+
expect(textareaNodeId).toBeGreaterThan(0);
162+
expect(selectNodeId).toBeGreaterThan(0);
163+
expect(submitNodeId).toBeGreaterThan(0);
164+
165+
// All should be unique
166+
const nodeIds = [emailNodeId, textareaNodeId, selectNodeId, submitNodeId];
167+
expect(new Set(nodeIds).size).toBe(4);
168+
});
169+
170+
test("returns node ID for dynamically created elements", async () => {
171+
const page = v3.context.pages()[0];
172+
173+
await page.goto(
174+
"data:text/html," +
175+
encodeURIComponent(
176+
`<!doctype html><html><body>
177+
<div id="container"></div>
178+
<script>
179+
const container = document.getElementById('container');
180+
const newBtn = document.createElement('button');
181+
newBtn.id = 'dynamic-btn';
182+
newBtn.textContent = 'Dynamically created';
183+
container.appendChild(newBtn);
184+
</script>
185+
</body></html>`,
186+
),
187+
);
188+
189+
const locator = page.locator("button#dynamic-btn");
190+
const nodeId = await locator.backendNodeId();
191+
192+
// Should work with dynamically created elements
193+
expect(typeof nodeId).toBe("number");
194+
expect(nodeId).toBeGreaterThan(0);
195+
});
196+
197+
test("returns node ID for elements with text selectors", async () => {
198+
const page = v3.context.pages()[0];
199+
200+
await page.goto(
201+
"data:text/html," +
202+
encodeURIComponent(
203+
`<!doctype html><html><body>
204+
<button>Submit Form</button>
205+
</body></html>`,
206+
),
207+
);
208+
209+
const locator = page.locator("text=Submit Form");
210+
const nodeId = await locator.backendNodeId();
211+
212+
// Should work with text-based selectors
213+
expect(typeof nodeId).toBe("number");
214+
expect(nodeId).toBeGreaterThan(0);
215+
});
216+
});

0 commit comments

Comments
 (0)