PageSourceSearch

https://playwright.dev/assets/js/b3916729.f870410f.js

js playwright.dev collected 2026-09-24 06:55:41 UTC 4,523 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkplaywright_dev=self.webpackChunkplaywright_dev||[]).push([["4019"],{18711(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>d,frontMatter:()=>o,contentTitle:()=>l,toc:()=>a,assets:()=>c});var s=JSON.parse('{"id":"extensibility","title":"Extensibility","description":"Custom selector engines","source":"@site/docs/extensibility.mdx","sourceDirName":".","slug":"/extensibility","permalink":"/docs/next/extensibility","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"id":"extensibility","title":"Extensibility"},"sidebar":"docs","previous":{"title":"Events","permalink":"/docs/next/events"},"next":{"title":"Frames","permalink":"/docs/next/frames"}}'),r=n(74848),i=n(28453);n(13554),n(41647),n(83137);let o={id:"extensibility",title:"Extensibility"},l,c={},a=[{value:"Custom selector engines",id:"custom-selector-engines",level:2}];function g(e){let t={a:"a",code:"code",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h2,{id:"custom-selector-engines",children:"Custom selector engines"}),"\n",(0,r.jsxs)(t.p,{children:["Playwright supports custom selector engines, registered with ",(0,r.jsx)(t.a,{href:"/docs/next/api/class-selectors#selectors-register",children:"selectors.register()"}),"."]}),"\n",(0,r.jsx)(t.p,{children:"Selector engine should have the following properties:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"query"})," function to query first element matching ",(0,r.jsx)(t.code,{children:"selector"})," relative to the ",(0,r.jsx)(t.code,{children:"root"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"queryAll"})," function to query all elements matching ",(0,r.jsx)(t.code,{children:"selector"})," relative to the ",(0,r.jsx)(t.code,{children:"root"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["By default the engine is run directly in the frame's JavaScript context and, for example, can call an application-defined function. To isolate the engine from any JavaScript in the frame, but leave access to the DOM, register the engine with ",(0,r.jsx)(t.code,{children:"{contentScript: true}"})," option. Content script engine is safer because it is protected from any tampering with the global objects, for example altering ",(0,r.jsx)(t.code,{children:"Node.prototype"})," methods. All built-in selector engines run as content scripts. Note that running as a content script is not guaranteed when the engine is used together with other custom engines."]}),"\n",(0,r.jsx)(t.p,{children:"Selectors must be registered before creating the page."}),"\n",(0,r.jsx)(t.p,{children:"An example of registering selector engine that queries elements based on a tag name:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-js",metastring:'title="baseTest.ts"',children:"import { test as base } from '@playwright/test';\n\nexport { expect } from '@playwright/test';\n\n// Must be a function that evaluates to a selector engine instance.\nconst createTagNameEngine = () => ({\n  // Returns the first element matching given selector in the root's subtree.\n  query(root, selector) {\n    return root.querySelector(selector);\n  },\n\n  // Returns all elements matching given selector in the root's subtree.\n  queryAll(root, selector) {\n    return Array.from(root.querySelectorAll(selector));\n  }\n});\n\nexport const test = base.extend<{}, { selectorRegistration: void }>({\n  // Register selectors once per worker.\n  selectorRegistration: [async ({ playwright }, use) => {\n    // Register the engine. Selectors will be prefixed with \"tag=\".\n    await playwright.selectors.register('tag', createTagNameEngine);\n    await use();\n  }, { scope: 'worker', auto: true }],\n});\n"})}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-js",metastring:'title="example.spec.ts"',children:"import { test, expect } from './baseTest';\n\ntest('selector engine test', async ({ page }) => {\n  // Now we can use 'tag=' selectors.\n  const button = page.locator('tag=button');\n  await button.click();\n\n  // We can combine it with built-in locators.\n  await page.locator('tag=div').getByText('Click me').click();\n\n  // We can use it in any methods supporting selectors.\n  await expect(page.locator('tag=button')).toHaveCount(3);\n});\n"})})]})}function d(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(g,{...e})}):g(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.