This original fixture represents an inventory table and an unrelated footer. It is local teaching markup, not a live website scrape:
<table id="stock"><tbody>
<tr><td data-col="sku">K7</td><td data-col="qty">4</td></tr>
<tr><td data-col="sku">K8</td><td data-col="qty">9</td></tr>
</tbody></table>
<footer><span data-col="qty">999</span></footer>
The target is the quantity for K8 inside stock: 9. The footer's 999 is outside that task. This exercise uses browser HTML and XPath evaluation, not the XML namespace problem covered by the XPath editor guide.
Establish the table context explicitly
MDN's Document.evaluate reference, checked October 24, 2026, defines the context-node parameter. In the following browser code, the table is the context node, and the expression starts with .// to search beneath that node.
const table = document.querySelector('#stock');
if (!table) throw new Error('Stock table missing');
const matches = document.evaluate(
".//tr[td[@data-col='sku' and normalize-space(.)='K8']]/td[@data-col='qty']",
table, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
);
if (matches.snapshotLength !== 1) {
throw new Error('Expected exactly one K8 quantity cell');
}
const quantityText = matches.snapshotItem(0).textContent.trim();
console.log(quantityText); // "9" for the fixture
This exact fixture and selector are locally executable in a browser. It returns text; it does not establish a numeric field's business validity or fetch an unseen page. Do not run code copied from a conversation in a sensitive page without reviewing it.
By contrast, // followed by a descendant search starts from the document root in this browser XPath context. An expression such as (//*[@data-col='qty'])[last()] returns the footer's 999 here. Passing table as the context does not turn that expression into a table-relative search.
Ask for negative cases with the proposed selector
Propose a read-only browser XPath for this supplied HTML.
[PASTE PERMITTED HTML]
Target table identifier: [ENTER]
Business key and exact matching rule: [ENTER]
Value cell and expected cardinality: [ENTER]
Explain the context node and why the expression stays inside it.
Return the expression, a small document.evaluate example and expected
matches. Do not use page-wide first/last match to conceal ambiguity.
Include missing-key, repeated-key and unrelated-outside-field tests.
Do not invent the target DOM, request credentials or claim execution.
Keep fixed sample literals separate from arbitrary user input; do not
build an XPath string by interpolating untrusted text without a proper
literal-escaping strategy.
The context and requested checks follow OpenAI's prompting guidance. The code above deliberately uses a fixed K8 literal; a reusable query builder needs separate handling for values containing quotes.
Reject missing or ambiguous matches
Change the supplied key to K99. The selector should return zero cells and the cardinality check should stop the extraction. Then add a second K8 row with quantity 12. It should return two cells and stop again, rather than silently choosing 9 or 12.
Finally, change the footer quantity from 999 to 888. The correctly scoped K8 result should remain 9. This tests the boundary, not merely whether the original number looked plausible. A nested table or a different row structure needs its own selector review; do not assume this descendant expression handles every table layout.
Run these cases against a permitted local copy before applying the selector to the actual target DOM. Retain the input snippet, exact expression, matched count and extracted text. If the page changes, the debugging guide helps turn the observed mismatch into a smaller diagnostic example.