1"use strict";(self.webpackChunkplaywright_dev=self.webpackChunkplaywright_dev||[]).push([["5081"],{56662(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>d,frontMatter:()=>o,contentTitle:()=>l,toc:()=>c,assets:()=>a});var s=JSON.parse('{"id":"extensibility","title":"Extensibility","description":"Custom selector engines","source":"@site/versioned_docs/version-stable/extensibility.mdx","sourceDirName":".","slug":"/extensibility","permalink":"/docs/extensibility","draft":false,"unlisted":false,"tags":[],"version":"stable","frontMatter":{"id":"extensibility","title":"Extensibility"},"sidebar":"docs","previous":{"title":"Events","permalink":"/docs/events"},"next":{"title":"Frames","permalink":"/docs/frames"}}'),i=n(74848),r=n(28453);n(13554),n(41647),n(83137);let o={id:"extensibility",title:"Extensibility"},l,a={},c=[{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,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h2,{id:"custom-selector-engines",children:"Custom selector engines"}),"\n",(0,i.jsxs)(t.p,{children:["Playwright supports custom selector engines, registered with ",(0,i.jsx)(t.a,{href:"/docs/api/class-selectors#selectors-register",children:"selectors.register()"}),"."]}),"\n",(0,i.jsx)(t.p,{children:"Selector engine should have the following properties:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:"query"})," function to query first element matching ",(0,i.jsx)(t.code,{children:"selector"})," relative to the ",(0,i.jsx)(t.code,{children:"root"}),"."]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:"queryAll"})," function to query all elements matching ",(0,i.jsx)(t.code,{children:"selector"})," relative to the ",(0,i.jsx)(t.code,{children:"root"}),"."]}),"\n"]}),"\n",(0,i.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,i.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,i.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,i.jsx)(t.p,{children:"Selectors must be registered before creating the page."}),"\n",(0,i.jsx)(t.p,{children:"An example of registering selector engine that queries elements based on a tag name:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.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,i.jsx)(t.pre,{children:(0,i.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,r.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.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.