[
  {
    "path": ".babelrc",
    "content": "{\n    \"presets\": [\n      [\"@babel/env\", {\n        \"targets\": {\n          \"browsers\": [\"last 2 versions\", \"IE >= 11\"]\n        }\n      }]\n    ],\n    \"comments\" : false\n  }"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    \"env\": {\n        \"browser\": true,\n        \"es2021\": true,\n        \"node\": true,\n    },\n    \"extends\": \"eslint:recommended\",\n    \"parserOptions\": {\n        \"ecmaVersion\": 12,\n        \"sourceType\": \"module\"\n    },\n    \"globals\": {\n        \"it\": true,\n        \"describe\": true,\n        \"expect\": true,\n        \"beforeEach\": true,\n        \"afterEach\": true,\n        \"jasmine\": true\n    },\n    \"rules\": { \"semi\": [\"error\", \"always\"]}\n};\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\nko_fi: griffadev\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "---\nname: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: ['14', '16']\n    steps:\n      - uses: actions/checkout@v4\n      - name: Use Node.js v${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Install dependencies\n        run: |\n          npm ci\n      - name: Build\n        run: npm run build --if-present\n      - name: Run Tests\n        run: |\n          npm run test:ci\n          npm run e2e:protractor\n        env:\n          CHROMEDRIVER_FILEPATH: /usr/local/share/chromedriver-linux64/chromedriver\n\n    timeout-minutes: 10\n"
  },
  {
    "path": ".gitignore",
    "content": "# IDEs\n.vscode\n.idea\n\n# Build artifacts\nnode_modules\ndist\ncoverage\n.DS_Store\n**/output/**\n"
  },
  {
    "path": ".npmignore",
    "content": ""
  },
  {
    "path": ".nvmrc",
    "content": "v14.21.3\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 George Griffiths\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/Georgegriff/query-selector-shadow-dom.svg?branch=main)](https://travis-ci.org/Georgegriff/query-selector-shadow-dom) [![npm version](https://badge.fury.io/js/query-selector-shadow-dom.svg)](https://badge.fury.io/js/query-selector-shadow-dom)\n\n# query-selector-shadow-dom\n\nquerySelector that can pierce Shadow DOM roots without knowing the path through nested shadow roots. Useful for automated testing of Web Components e.g. with Selenium, Puppeteer.\n\n```javascript\n// available as an ES6 module for importing in Browser environments\n\nimport {\n  querySelectorAllDeep,\n  querySelectorDeep,\n} from \"query-selector-shadow-dom\";\n```\n\n## What is a nested shadow root?\n\n![Image of Shadow DOM elements in dev tools](./Chrome-example.png)\nYou can see that `.dropdown-item:not([hidden])` (Open downloads folder) is several layers deep in shadow roots, most tools will make you do something like\n\n```javascript\ndocument\n  .querySelector(\"body > downloads-manager\")\n  .shadowRoot.querySelector(\"#toolbar\")\n  .shadowRoot.querySelector(\".dropdown-item:not([hidden])\");\n```\n\nEW!\n\nwith query-selector-shadow-dom:\n\n```javascript\nimport {\n  querySelectorAllDeep,\n  querySelectorDeep,\n} from \"query-selector-shadow-dom\";\nquerySelectorDeep(\".dropdown-item:not([hidden])\");\n```\n\n## API\n\n- querySelectorAllDeep - mirrors `querySelectorAll` from the browser, will return an `Array` of elements matching the query\n- querySelectorDeep - mirrors `querySelector` from the browser, will return the `first` matching element of the query.\n- collectAllElementsDeep - collects all elements on the page, including shadow dom\n\nBoth of the methods above accept a 2nd parameter, see section `Provide alternative node`. This will change the starting element to search from i.e. it will find ancestors of that node that match the query.\n\n## Known limitations\n\n- Source ordering of results may not be preserved. Due to the nature of how this library works, by breaking down selectors into parts, when using multiple selectors (e.g. split by commas) the results will be based on the order of the query, not the order the result appear in the dom. This is different from the native `querySelectorAll` functionality. You can read more about this here: https://github.com/Georgegriff/query-selector-shadow-dom/issues/54\n\n## Plugins\n\n### WebdriverIO\n\nThis plugin implements a custom selector strategy: https://webdriver.io/docs/selectors.html#custom-selector-strategies\n\n```javascript\n// make sure you have selenium standalone running\nconst { remote } = require(\"webdriverio\");\nconst {\n  locatorStrategy,\n} = require(\"query-selector-shadow-dom/plugins/webdriverio\");\n\n(async () => {\n  const browser = await remote({\n    logLevel: \"error\",\n    path: \"/wd/hub\",\n    capabilities: {\n      browserName: \"chrome\",\n    },\n  });\n\n  // The magic - registry custom strategy\n  browser.addLocatorStrategy(\"shadow\", locatorStrategy);\n\n  // now you have a `shadow` custom locator.\n\n  // All elements on the page\n  await browser.waitUntil(() =>\n    browser.custom$(\"shadow\", \".btn-in-shadow-dom\")\n  );\n  const elements = await browser.$$(\"*\");\n\n  const elementsShadow = await browser.custom$$(\"shadow\", \"*\");\n\n  console.log(\"All Elements on Page Excluding Shadow Dom\", elements.length);\n  console.log(\n    \"All Elements on Page Including Shadow Dom\",\n    elementsShadow.length\n  );\n\n  await browser.url(\"http://127.0.0.1:5500/test/\");\n  // find input element in shadow dom\n  const input = await browser.custom$(\"shadow\", \"#type-to-input\");\n  // type to input ! Does not work in firefox, see above.\n  await input.setValue(\"Typed text to input\");\n  // Firefox workaround\n  // await browser.execute((input, val) => input.value = val, input, 'Typed text to input')\n\n  await browser.deleteSession();\n})().catch((e) => console.error(e));\n```\n\n#### How is this different to `shadow$`\n\n`shadow$` only goes one level deep in a shadow root.\n\nTake this example.\n![Image of Shadow DOM elements in dev tools](./Chrome-example.png)\nYou can see that `.dropdown-item:not([hidden])` (Open downloads folder) is several layers deep in shadow roots, but this library will find it, `shadow$` would not.\nYou would have to construct a path via css or javascript all the way through to find the right element.\n\n```javascript\nconst { remote } = require(\"webdriverio\");\nconst {\n  locatorStrategy,\n} = require(\"query-selector-shadow-dom/plugins/webdriverio\");\n\n(async () => {\n  const browser = await remote({ capabilities: { browserName: \"chrome\" } });\n\n  browser.addLocatorStrategy(\"shadow\", locatorStrategy);\n\n  await browser.url(\"chrome://downloads\");\n  const moreActions = await browser.custom$(\"shadow\", \"#moreActions\");\n  await moreActions.click();\n  const span = await browser.custom$(\"shadow\", \".dropdown-item:not([hidden])\");\n  const text = await span.getText();\n  // prints `Open downloads folder`\n  console.log(text);\n\n  await browser.deleteSession();\n})().catch((e) => console.error(e));\n```\n\n#### Known issues\n\n- https://webdriver.io/blog/2019/02/22/shadow-dom-support.html#browser-support\n\n- From the above, firefox `setValue` does NOT currently work.\n  `. A workaround for now is to use a custom command (or method on your component object) that sets the input field's value via browser.execute(function).`\n\n- Safari pretty much doesn't work, not really a surprise.\n\nThere are some webdriver examples available in the examples folder of this repository.\n[WebdriverIO examples](https://github.com/Georgegriff/query-selector-shadow-dom/blob/main/examples/webdriverio)\n\n### Puppeteer\n\nUpdate: As of 5.4.0 Puppeteer now has a built in shadow Dom selector, this module might not be required for Puppeteer anymore.\nThey don't have any documentation: https://github.com/puppeteer/puppeteer/pull/6509\n\nThere are some puppeteer examples available in the examples folder of this repository.\n\n[Puppeteer examples](https://github.com/Georgegriff/query-selector-shadow-dom/blob/main/examples/puppeteer)\n\n### Playwright\n\nUpdate: as of Playwright v0.14.0 their CSS and text selectors work with shadow Dom out of the box, you don't need this library anymore for Playwright.\n\nPlaywright works really nicely with this package.\n\nThis module exposes a playwright `selectorEngine`: https://github.com/microsoft/playwright/blob/main/docs/api.md#selectorsregisterenginefunction-args\n\n```javascript\nconst { selectorEngine } = require(\"query-selector-shadow-dom/plugins/playwright\");\nconst playwright = require('playwright');\n\n await selectors.register('shadow', createTagNameEngine);\n...\n  await page.goto('chrome://downloads');\n  // shadow= allows a css query selector that automatically pierces shadow roots.\n  await page.waitForSelector('shadow=#no-downloads span', {timeout: 3000})\n```\n\nFor a full example see: https://github.com/Georgegriff/query-selector-shadow-dom/blob/main/examples/playwright\n\n### Protractor\n\nThis project provides a Protractor plugin, which can be enabled in your [`protractor.conf.js`](https://www.protractortest.org/#/api-overview) file:\n\n```javascript\nexports.config = {\n  plugins: [\n    {\n      package: \"query-selector-shadow-dom/plugins/protractor\",\n    },\n  ],\n\n  // ... other Protractor-specific config\n};\n```\n\nThe plugin registers a new [locator](https://www.protractortest.org/#/api?view=ProtractorBy) - `by.shadowDomCss(selector /* string */)`, which can be used in regular Protractor tests:\n\n```javascript\nelement(by.shadowDomCss(\"#item-in-shadow-dom\"));\n```\n\nThe locator also works with [Serenity/JS](https://serenity-js.org) tests that [use Protractor](https://serenity-js.org/modules/protractor) under the hood:\n\n```typescript\nimport \"query-selector-shadow-dom/plugins/protractor\";\nimport { Target } from \"@serenity-js/protractor\";\nimport { by } from \"protractor\";\n\nconst ElementOfInterest = Target.the(\"element of interest\").located(\n  by.shadowDomCss(\"#item-in-shadow-dom\")\n);\n```\n\nSee the [end-to-end tests](https://github.com/Georgegriff/query-selector-shadow-dom/blob/features/protractor-locator/test/protractor-locator.e2e.js) for more examples.\n\n## Examples\n\n### Provide alternative node\n\n```javascript\n// query from another node\nquerySelectorShadowDom.querySelectorAllDeep(\n  \"child\",\n  document.querySelector(\"#startNode\")\n);\n// query an iframe\nquerySelectorShadowDom.querySelectorAllDeep(\"child\", iframe.contentDocument);\n```\n\nThis library does not allow you to query across iframe boundaries, you will need to get a reference to the iframe you want to interact with. </br>\nIf your iframe is inside of a shadow root you could use `querySelectorDeep` to find the iframe, then pass the `contentDocument` into the 2nd argument of `querySelectorDeep` or `querySelectorAllDeep`.\n\n### Chrome downloads page\n\nIn the below examples the components being searched for are nested within web components `shadowRoots`.\n\n```javascript\n// Download and Paste the lib code in dist into chrome://downloads console to try it out :)\n\nconsole.log(\n  querySelectorShadowDom.querySelectorAllDeep(\n    \"downloads-item:nth-child(4) #remove\"\n  )\n);\nconsole.log(\n  querySelectorShadowDom.querySelectorAllDeep(\n    '#downloads-list .is-active a[href^=\"https://\"]'\n  )\n);\nconsole.log(\n  querySelectorShadowDom.querySelectorDeep(\"#downloads-list div#title-area + a\")\n);\n```\n\n# Shady DOM\n\nIf using the polyfills and shady DOM, this library will still work.\n\n## Importing\n\n- Shipped as an ES6 module to be included using a bundler of your choice (or not).\n- ES5 version bundled on top the window as `window.querySelectorShadowDom` available for easy include into a test framework\n\n## Running the code locally\n\n`npm install`\n\n### Running the tests\n\n`npm test`\n\n### Running the tests in watch mode\n\n`npm run watch`\n\n### Running the build\n\n`npm run build`\n"
  },
  {
    "path": "codecept.conf.js",
    "content": "const { setHeadlessWhen } = require('@codeceptjs/configure');\n\n// turn on headless mode when running with HEADLESS=true environment variable\n// HEADLESS=true npx codecept run\nsetHeadlessWhen(process.env.HEADLESS);\n\nexports.config = {\n  tests: 'test/codeceptjs/*.test.js',\n  output: './output',\n  helpers: {\n    Playwright: {\n      url: 'http://localhost',\n      show: true,\n      browser: 'chromium'\n    }\n  },\n  include: {\n    I: './steps_file.js'\n  },\n  bootstrap: null,\n  mocha: {},\n  name: 'query-selector-shadow-dom',\n  plugins: {\n    retryFailedStep: {\n      enabled: true\n    },\n    screenshotOnFail: {\n      enabled: true\n    }\n  }\n}"
  },
  {
    "path": "demo.js",
    "content": "// Paste this code into chrome://downloads console to try it out :)\n\nconsole.log(querySelectorDeep('downloads-item:nth-child(4) #remove'));\nconsole.log(querySelectorDeep('#downloads-list .is-active a[href^=\"https://\"]'));\nconsole.log(querySelectorDeep('#downloads-list div#title-area + a'));"
  },
  {
    "path": "examples/playwright/custom-engine.js",
    "content": "const { selectorEngine } = require(\"query-selector-shadow-dom/plugins/playwright\");\nconst playwright = require('playwright')\n\nconst main = async () => {\n  await playwright.selectors.register('shadow', selectorEngine)\n\n  const browser = await playwright.chromium.launch({ headless: false})\n  const context = await browser.newContext({ viewport: null })\n  const page = await context.newPage()\n\n  await page.goto('chrome://downloads')\n\n  await page.waitForSelector('shadow=#no-downloads span', {timeout: 3000})\n  await new Promise(resolve => setTimeout(resolve, 3000))   \n\n  await page.close()\n  await context.close()\n  await browser.close()\n}\n\nmain()"
  },
  {
    "path": "examples/puppeteer/clicking-elements.js",
    "content": "const puppeteer = require('puppeteer');\nconst {  QueryHandler } = require(\"query-selector-shadow-dom/plugins/puppeteer\");\n(async () => {\n    try {\n        await puppeteer.registerCustomQueryHandler('shadow', QueryHandler);\n        const browser = await puppeteer.launch({\n            headless: false,\n            devtools: true\n        })\n        const page = await browser.newPage()\n        await page.goto('http://127.0.0.1:5500/test/')\n\n        // ensure btn exists and return it\n        await page.waitForSelector(\"shadow/.btn-in-shadow-dom\");\n        const btn = await page.$(\"shadow/.btn-in-shadow-dom\");\n        await btn.click();\n        // check btn was clicked (this page expected btn to change text of output)\n        const outputSpan = await page.$(\"shadow/.output\");\n        const text = await page.evaluate((output) => output.innerText, outputSpan);\n        // prints the text from the output\n        console.log(text);\n\n        await browser.close()\n    } catch (e) {\n        console.error(e);\n    }\n\n})()\n"
  },
  {
    "path": "examples/puppeteer/custom-engine.js",
    "content": "const {  QueryHandler } = require(\"query-selector-shadow-dom/plugins/puppeteer\");\nconst puppeteer = require('puppeteer');\n\nconst main = async () => {\n  await puppeteer.registerCustomQueryHandler('shadow', QueryHandler);\n\n  const browser = await puppeteer.chromium.launch({ headless: false});\n  const context = await browser.newContext({ viewport: null });\n  const page = await context.newPage();\n\n  await page.goto('chrome://downloads');\n\n  const element = await page.waitForSelector('shadow/div', {timeout: 3000});\n  const span = await element.$$(\"shadow/div > .illustration + span\");\n  console.log(span);\n  await new Promise(resolve => setTimeout(resolve, 3000));\n\n  await page.close()\n  await context.close()\n  await browser.close()\n}\n\nmain()\n"
  },
  {
    "path": "examples/puppeteer/multiple-elements.js",
    "content": "const puppeteer = require('puppeteer');\nconst {  QueryHandler } = require(\"query-selector-shadow-dom/plugins/puppeteer\");\n(async () => {\n    try {\n        await puppeteer.registerCustomQueryHandler('shadow', QueryHandler);\n        const browser = await puppeteer.launch({\n            headless: false\n        })\n        const page = await browser.newPage()\n        await page.goto('http://127.0.0.1:5500/test/')\n        // wait for a web component to appear\n        await page.waitForSelector(\"shadow/.btn-in-shadow-dom\")\n        const elements = await page.$$(\"*\");\n        const elementsShadow = await page.$$(\"shadow/*\");\n        console.log(\"All Elements on Page Excluding Shadow Dom\", elements.length);\n        console.log(\"All Elements on Page Including Shadow Dom\", elementsShadow.length);\n        await browser.close()\n\n    } catch (e) {\n        console.error(e);\n    }\n})()\n"
  },
  {
    "path": "examples/puppeteer/typing-to-elements.js",
    "content": "const puppeteer = require('puppeteer');\nconst {  QueryHandler } = require(\"query-selector-shadow-dom/plugins/puppeteer\");\n(async () => {\n    try {\n        await puppeteer.registerCustomQueryHandler('shadow', QueryHandler);\n        const browser = await puppeteer.launch({\n            headless: false\n        })\n        const page = await browser.newPage()\n        await page.goto('http://127.0.0.1:5500/test/')\n\n        const inputElement = await page.waitForSelector(\"shadow/#type-to-input\");\n\n        await inputElement.type(\"Typed text to input\");\n\n        const value = await page.evaluate(inputElement => inputElement.value, inputElement);\n        console.log(\"Value\", value);\n\n        await browser.close()\n\n    } catch (e) {\n        console.error(e);\n    }\n\n})()\n"
  },
  {
    "path": "examples/webdriverio/deeply-nested.js",
    "content": "const { remote } = require('webdriverio')\nconst { locatorStrategy } = require('query-selector-shadow-dom/plugins/webdriverio');\n\n;(async () => {\n    const browser = await remote({\n        logLevel: 'error',\n        path: '/wd/hub',\n        capabilities: {\n            browserName: 'chrome'\n        }\n    })\n\n    browser.addLocatorStrategy('shadow', locatorStrategy);\n\n    \n    await browser.url('chrome://downloads')\n    const moreActions = await browser.custom$('shadow', '#moreActions');\n    await moreActions.click();\n    const span = await browser.custom$('shadow', '.dropdown-item:not([hidden])');\n    const text = await span.getText()\n    // prints `Open downloads folder`\n    console.log(text);\n\n    await browser.deleteSession()\n})().catch((e) => console.error(e))"
  },
  {
    "path": "examples/webdriverio/multiple-elements.js",
    "content": "const { remote } = require('webdriverio')\nconst { locatorStrategy } = require('query-selector-shadow-dom/plugins/webdriverio');\n\n;(async () => {\n    const browser = await remote({\n        logLevel: 'error',\n        path: '/wd/hub',\n        capabilities: {\n            browserName: 'firefox'\n        }\n    })\n\n    browser.addLocatorStrategy('shadow', locatorStrategy);\n\n    \n    await browser.url('http://127.0.0.1:5500/test/')\n    await browser.waitUntil(() => browser.custom$(\"shadow\", \".btn-in-shadow-dom\"));\n    const elements = await browser.$$(\"*\");\n    const elementsShadow = await browser.custom$$(\"shadow\", \"*\");\n    console.log(\"All Elements on Page Excluding Shadow Dom\", elements.length);\n    console.log(\"All Elements on Page Including Shadow Dom\", elementsShadow.length);\n\n    await browser.deleteSession()\n})().catch((e) => console.error(e))"
  },
  {
    "path": "examples/webdriverio/typing-to-elements.js",
    "content": "const { remote } = require('webdriverio')\nconst { locatorStrategy } = require('query-selector-shadow-dom/plugins/webdriverio');\n\n;(async () => {\n    const browser = await remote({\n        logLevel: 'error',\n        path: '/wd/hub',\n        capabilities: {\n            browserName: 'firefox'\n        }\n    })\n\n    browser.addLocatorStrategy('shadow', locatorStrategy);\n\n    \n    await browser.url('http://127.0.0.1:5500/test/')\n    const input = await browser.custom$('shadow', '#type-to-input');\n    await input.setValue('Typed text to input');\n    // Firefox workaround\n   // await browser.execute((input, val) => input.value = val, input, 'Typed text to input')\n    console.log(await input.getValue())\n\n    await browser.deleteSession()\n})().catch((e) => console.error(e))"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true\n  }\n}"
  },
  {
    "path": "karma.common.js",
    "content": "const babel = require('rollup-plugin-babel');\nconst babelrc = require('babelrc-rollup').default;\nconst istanbul = require('rollup-plugin-istanbul');\n\nconst babelConfig = {\n    'presets': [\n        ['@babel/preset-env', {\n            'targets': {\n                'browsers': ['ff >= 60']\n            },\n            'loose': true\n        }]\n    ]\n};\n\nmodule.exports = function(overrides, config) {\n    return Object.assign({\n        // base path that will be used to resolve all patterns (eg. files, exclude)\n        basePath: '',\n\n\n        // frameworks to use\n        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n        frameworks: ['jasmine'],\n\n\n        // list of files / patterns to load in the browser\n        files: [\n            { pattern: 'node_modules/@webcomponents/webcomponentsjs/bundles/**.js', served: true, included: true },\n            { pattern: 'node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js', served: true, included: true },\n            'test/**/*.spec.js'\n        ],\n\n\n        // list of files / patterns to exclude\n        exclude: [],\n\n\n        // preprocess matching files before serving them to the browser\n        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n        preprocessors: {\n            'src/**/*.js': ['rollup', 'coverage'],\n            'test/**/*.js': ['rollup']\n        },\n\n        rollupPreprocessor: {\n            /**\n             * This is just a normal Rollup config object,\n             * except that `input` is handled for you.\n             */\n            plugins: [\n                istanbul({\n                    exclude: ['test/**/*.js']\n                }),\n                babel(babelrc({\n                    addExternalHelpersPlugin: false,\n                    config: babelConfig,\n                    exclude: 'node_modules/**'\n                }))\n\n            ],\n            output: {\n                format: 'iife', // Helps prevent naming collisions.\n                name: 'querySelectorShadowDom', // Required for 'iife' format.\n                sourcemap: 'inline' // Sensible for testing.\n            }\n        },\n\n        // test results reporter to use\n        // possible values: 'dots', 'progress'\n        // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n        reporters: ['spec', 'coverage'],\n        specReporter: {\n            maxLogLines: 5, // limit number of lines logged per test\n            suppressErrorSummary: true, // do not print error summary\n            suppressFailed: false, // do not print information about failed tests\n            suppressPassed: false, // do not print information about passed tests\n            suppressSkipped: true, // do not print information about skipped tests\n            showSpecTiming: true, // print the time elapsed for each spec\n            failFast: true // test would finish with error when a first fail occurs. \n        },\n        coverageReporter: {\n            type: 'lcov', // lcov or lcovonly are required for generating lcov.info files\n            dir: 'coverage/'\n        },\n        // web server port\n        port: 9876,\n\n\n        // enable / disable colors in the output (reporters and logs)\n        colors: true,\n\n\n        // level of logging\n        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n        logLevel: config.LOG_INFO,\n\n\n        // enable / disable watching file and executing tests whenever any file changes\n        autoWatch: true,\n\n\n        // Continuous Integration mode\n        // if true, Karma captures browsers, runs the tests and exits\n        singleRun: true,\n\n        // Concurrency level\n        // how many browser should be started simultaneous\n        concurrency: Infinity\n    }, overrides);\n};"
  },
  {
    "path": "karma.conf.js",
    "content": "const KarmaConfig = require('./karma.common.js');\nmodule.exports = function(config) {\n\n    config.set(KarmaConfig({\n        browsers: ['ChromeHeadless', 'Firefox']\n    }, config));\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"query-selector-shadow-dom\",\n  \"version\": \"1.0.1\",\n  \"description\": \"use querySelector syntax to search for nodes inside of (nested) shadow roots\",\n  \"main\": \"src/querySelectorDeep.js\",\n  \"scripts\": {\n    \"prepublish\": \"npm run build\",\n    \"prebuild\": \"npm run lint\",\n    \"lint\": \"eslint src/**/*.js\",\n    \"build\": \"rollup -c\",\n    \"test\": \"karma start\",\n    \"test:ci\": \"karma start --browsers ChromeHeadless,FirefoxHeadless\",\n    \"e2e:protractor\": \"protractor protractor.conf.js\",\n    \"watch\": \"npm-watch\",\n    \"selenium\": \"./node_modules/.bin/selenium-standalone install && ./node_modules/.bin/selenium-standalone start\"\n  },\n  \"watch\": {\n    \"test\": \"{src,test}/*.js\"\n  },\n  \"files\": [\n    \"Chrome-example.png\",\n    \"/src/\",\n    \"dist/querySelectorShadowDom.js\",\n    \"/plugins/\"\n  ],\n  \"author\": \"George Griffiths <GeorgeGriff>\",\n  \"keywords\": [\n    \"webcomponents\",\n    \"puppeteer\",\n    \"playwright\",\n    \"automation\",\n    \"queryselector\",\n    \"shadowdom\",\n    \"web-components\",\n    \"testing\",\n    \"webdriver\",\n    \"protractor\",\n    \"selenium\",\n    \"webdriverio\",\n    \"codeceptjs\"\n  ],\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.1.2\",\n    \"@babel/preset-env\": \"^7.1.0\",\n    \"@wdio/selenium-standalone-service\": \"^6.4.0\",\n    \"@webcomponents/webcomponentsjs\": \"^2.0.2\",\n    \"babelrc-rollup\": \"^3.0.0\",\n    \"eslint\": \"^7.19.0\",\n    \"jasmine\": \"^3.1.0\",\n    \"karma\": \"^6.2.0\",\n    \"karma-chrome-launcher\": \"^2.2.0\",\n    \"karma-coverage\": \"^2.0.3\",\n    \"karma-firefox-launcher\": \"^1.1.0\",\n    \"karma-jasmine\": \"^1.1.2\",\n    \"karma-rollup-preprocessor\": \"^7.0.6\",\n    \"karma-spec-reporter\": \"0.0.32\",\n    \"npm-watch\": \"^0.7.0\",\n    \"protractor\": \"^7.0.0\",\n    \"puppeteer\": \"^5.2.0\",\n    \"rollup\": \"^2.41.2\",\n    \"rollup-plugin-babel\": \"^4.0.3\",\n    \"rollup-plugin-istanbul\": \"^2.0.1\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-terser\": \"^7.0.2\",\n    \"selenium-standalone\": \"^6.19.0\",\n    \"webdriverio\": \"^6.4.5\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/webdriverio/query-selector-shadow-dom\"\n  }\n}\n"
  },
  {
    "path": "plugins/codeceptjs/README.md",
    "content": "# Support for Shadow DOM in CodeceptJS\n\n- Supported CodeceptJS Helpers: *Playwright* only.\n- CodeceptJS 2.6.0 released webdriver.io support as an alternative if you don't want to use Playwright\n\nSupport for this plugin is currently limited to Playwright, this is mostly due to the fact that playwright\nallows for the addition of `custom selector engines`.\n\nGoal/Example: To be able to write a test that works easily with shadow dom web components.\nSee Issues for what currently works and what doesn't\n\n```javascript\nFeature(\"The chrome downloads page\");\nScenario(\"Can interact with the search box\", async I => {\n  I.amOnPage(\"chrome://downloads\");\n  I.see(\"Files you download appear here\", {shadow: \"#no-downloads span\"});\n  I.waitForVisible({shadow: \"#no-downloads\"}, 5);\n  I.click({shadow: `[title=\"Search downloads\"]`});\n  I.waitForVisible({shadow: '#searchInput'}, 5);\n  I.fillField({shadow: '#searchInput'}, \"A download\")\n  I.waitForValue({shadow: '#searchInput'}, \"A download\", 5)\n  I.waitForText(\"No search results found\", 3, {shadow: \"#no-downloads span\"});\n  I.clearField({shadow: '#searchInput'})\n  I.waitForValue({shadow: '#searchInput'}, \"\", 5)\n});\n\n```\n\nSetup:\n\n1. `npm install query-selector-shadow-dom codeceptjs playwright`\n2. Setup a codeceptjs project: https://codecept.io/quickstart/\n3. In `codecept.config.js` add this shadow dom plugin\n\n```javascript\n plugins: {\n    shadowDom: {\n      enabled: true,\n      locator: \"shadow\",\n      require: \"query-selector-shadow-dom/plugins/codeceptjs\"\n    }\n  }\n```\n4. Start using the custom locator `{shadow: \"...\"}` You may rename the locator in the config file from \"shadow\" to something else.\n5. Read issues below as not everything currently works.\n\nIssues:\n\n## What works\n- Most of the APIs listed here should work with shadow dom https://codecept.io/helpers/Playwright/#playwright\n\n### The following methods are not supported as of right now:\n- waitForNoVisibleElements (looking for help should be do-able, feel free to PR to CodeceptJS)\n"
  },
  {
    "path": "plugins/codeceptjs/index.js",
    "content": "const { selectorEngine } = require(\"../playwright\");\nconst supportedHelpers = [\n    'Playwright'\n]\nconst playwright = require('playwright');\n\nmodule.exports = function(config) {\n    const container = codeceptjs.container;\n    const event = codeceptjs.event;\n    const helpers = container.helpers()\n    let helperName\n    for (helperName of supportedHelpers) {\n        if (Object.keys(helpers).indexOf(helperName) > -1) {\n          helper = helpers[helperName];\n        }\n    }\n    if (!helper) {\n        throw new Error(`Shadow dom plugin only supports: ${supportedHelpers.join(',')}`)\n    }\n    if (!config) {\n        config = {}\n    }\n    if (!config.locator) {\n        config.locator = \"shadow\"\n    }\n\n    event.dispatcher.on(event.suite.before, async () => {\n        if(helperName === \"Playwright\") {\n            // temp handle api change in playwright may need to move to major version lib for documentation\n            try {\n                await playwright.selectors.register(selectorEngine, { name: config.locator });\n            } catch(e) {\n                await playwright.selectors.register(config.locator, selectorEngine);\n            }\n        }\n    });\n}"
  },
  {
    "path": "plugins/playwright/index.js",
    "content": "\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\n// load the library in UMD format which self executes and adds window.querySelectorShadowDom\nconst querySelectorShadowDomUMD = fs.readFileSync(path.resolve(__dirname, \"../../dist/querySelectorShadowDom.js\"))\n\n// a string because playwright does a .toString on a selector engine and we need to\n// make sure that query-selector-shadow-dom is injected and loaded into the function closure\nconst engineString =`\n    ${querySelectorShadowDomUMD}\n    return {\n        create(root, target) {\n            return undefined;\n        },\n        query(root, selector) {\n            return querySelectorShadowDom.querySelectorDeep(selector, root);\n        },\n        queryAll(root, selector) {\n            return querySelectorShadowDom.querySelectorAllDeep(selector, root);\n        }\n    }\n`\nconst selectorEngine = new Function(\"\", engineString)\n\nmodule.exports = { selectorEngine };"
  },
  {
    "path": "plugins/protractor/index.d.ts",
    "content": "declare module 'protractor' {\n    import { Locator } from 'protractor';\n\n    export interface ProtractorBy {\n        /**\n         * Find element within the Shadow DOM.\n         *\n         * @param {string} selector\n         * @returns {Locator} location strategy\n         */\n        shadowDomCss(selector: string): Locator;\n    }\n}\n"
  },
  {
    "path": "plugins/protractor/index.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst querySelectorAllDeep = fs.readFileSync(path.resolve(__dirname, \"../../dist/querySelectorShadowDom.js\"))\n\nmodule.exports = {\n    name: 'query-selector-shadow-dom',\n    onPrepare: function() {\n        global.by.addLocator('shadowDomCss', `\n            var selector /* string */ = arguments[0];\n            var parentElement /* WebElement? */ = arguments[1];\n            var rootSelector /* string? */ = arguments[2];\n            \n            ${ querySelectorAllDeep }\n\n            return querySelectorShadowDom.querySelectorAllDeep(selector, parentElement || document)\n        `);\n    }\n}\n"
  },
  {
    "path": "plugins/puppeteer/index.js",
    "content": "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nconst querySelectorShadowDomUMD = fs.readFileSync(path.resolve(__dirname, \"../../dist/querySelectorShadowDom.js\"))\n\nconst QueryHandler = {\n    queryOne: new Function('element', 'selector', `\n        ${querySelectorShadowDomUMD}\n        return querySelectorShadowDom.querySelectorDeep(selector, element);\n    `),\n    queryAll: new Function('element', 'selector', `\n    ${querySelectorShadowDomUMD}\n        return querySelectorShadowDom.querySelectorAllDeep(selector, element);\n    `)\n};\nmodule.exports.QueryHandler = QueryHandler;"
  },
  {
    "path": "plugins/webdriverio/index.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst querySelectorAllDeep = fs.readFileSync(path.resolve(__dirname, \"../../dist/querySelectorShadowDom.js\"))\n\nconst selectorFunction = new Function('selector', 'element', `\n${querySelectorAllDeep}\nreturn querySelectorShadowDom.querySelectorAllDeep(selector, element);\n`);\n\nmodule.exports.locatorStrategy = selectorFunction;"
  },
  {
    "path": "protractor.conf.js",
    "content": "exports.config = {\n    baseUrl: 'http://localhost:3000',\n\n    chromeDriver: process.env.CHROMEDRIVER_FILEPATH || require(`chromedriver/lib/chromedriver`).path,\n    SELENIUM_PROMISE_MANAGER: false,\n    directConnect: true,\n\n    // https://github.com/angular/protractor/blob/main/docs/timeouts.md\n    allScriptsTimeout: 110000,\n\n    specs: [ './test/protractor-locator.e2e.js', ],\n\n    plugins: [{\n        path: './plugins/protractor'\n    }],\n\n    onPrepare: function() {\n        global.browser.waitForAngularEnabled(false);\n    },\n\n    capabilities: {\n        browserName: 'chrome',\n\n        chromeOptions: {\n            args: [\n                '--no-sandbox',\n                '--disable-infobars',\n                '--disable-dev-shm-usage',\n                '--disable-extensions',\n                '--log-level=3',\n                '--disable-gpu',\n                '--window-size=1920,1080',\n                '--headless'\n            ]\n        }\n    }\n};\n"
  },
  {
    "path": "puppeteer-es5.js",
    "content": "const puppeteer = require('puppeteer');\nconst path = require('path');\nconst fs = require('fs');\n(async() => {\n    try {\n        const browser = await puppeteer.launch()\n        const page = await browser.newPage()\n        await page.goto('https://www.polymer-project.org/2.0/docs/upgrade')\n        await page.addScriptTag({\n            path: path.join(__dirname, 'node_modules/query-selector-shadow-dom/dist/querySelectorShadowDom.js')\n        });\n\n        // execute standard javascript in the context of the page.\n        const downloads = await page.evaluate(() => {\n            const anchors = Array.from(querySelectorShadowDom.querySelectorAllDeep('a'))\n            return anchors.map(anchor => anchor.href)\n        })\n        console.log(downloads)\n        await browser.close()\n    } catch (e) {\n        console.error(e);\n    }\n\n})()"
  },
  {
    "path": "rollup.config.js",
    "content": "import babel from 'rollup-plugin-babel';\nimport babelrc from 'babelrc-rollup';\nimport { terser } from 'rollup-plugin-terser';\n\nconst babelConfig = {\n    'presets': [\n        ['@babel/preset-env', {\n            'targets': {\n                'browsers': ['last 2 versions', 'IE >= 11']\n            },\n            'loose': true\n        }]\n    ]\n};\n\nexport default {\n    input: 'src/querySelectorDeep.js',\n    plugins: [\n        babel(babelrc({\n            addExternalHelpersPlugin: false,\n            config: babelConfig,\n            exclude: 'node_modules/**'\n        })),\n        terser()\n    ],\n    output: {\n        format: 'iife',\n        name: 'querySelectorShadowDom',\n        file: 'dist/querySelectorShadowDom.js'\n    }\n};"
  },
  {
    "path": "src/normalize.js",
    "content": "/* istanbul ignore file */\n\n\n// normalize-selector-rev-02.js\n/*\n  author: kyle simpson (@getify)\n  original source: https://gist.github.com/getify/9679380\n\n  modified for tests by david kaye (@dfkaye)\n  21 march 2014\n\n  rev-02 incorporate kyle's changes 3/2/42014\n*/\n\nexport function normalizeSelector(sel) {\n  // save unmatched text, if any\n  function saveUnmatched() {\n    if (unmatched) {\n      // whitespace needed after combinator?\n      if (tokens.length > 0 && /^[~+>]$/.test(tokens[tokens.length - 1])) {\n        tokens.push(\" \");\n      }\n\n      // save unmatched text\n      tokens.push(unmatched);\n    }\n  }\n\n  var tokens = [],\n    match,\n    unmatched,\n    regex,\n    state = [0],\n    next_match_idx = 0,\n    prev_match_idx,\n    not_escaped_pattern = /(?:[^\\\\]|(?:^|[^\\\\])(?:\\\\\\\\)+)$/,\n    whitespace_pattern = /^\\s+$/,\n    state_patterns = [\n      /\\s+|\\/\\*|[\"'>~+[(]/g, // general\n      /\\s+|\\/\\*|[\"'[\\]()]/g, // [..] set\n      /\\s+|\\/\\*|[\"'[\\]()]/g, // (..) set\n      null, // string literal (placeholder)\n      /\\*\\//g, // comment\n    ];\n  sel = sel.trim();\n\n  // eslint-disable-next-line no-constant-condition\n  while (true) {\n    unmatched = \"\";\n\n    regex = state_patterns[state[state.length - 1]];\n\n    regex.lastIndex = next_match_idx;\n    match = regex.exec(sel);\n\n    // matched text to process?\n    if (match) {\n      prev_match_idx = next_match_idx;\n      next_match_idx = regex.lastIndex;\n\n      // collect the previous string chunk not matched before this token\n      if (prev_match_idx < next_match_idx - match[0].length) {\n        unmatched = sel.substring(\n          prev_match_idx,\n          next_match_idx - match[0].length\n        );\n      }\n\n      // general, [ ] pair, ( ) pair?\n      if (state[state.length - 1] < 3) {\n        saveUnmatched();\n\n        // starting a [ ] pair?\n        if (match[0] === \"[\") {\n          state.push(1);\n        }\n        // starting a ( ) pair?\n        else if (match[0] === \"(\") {\n          state.push(2);\n        }\n        // starting a string literal?\n        else if (/^[\"']$/.test(match[0])) {\n          state.push(3);\n          state_patterns[3] = new RegExp(match[0], \"g\");\n        }\n        // starting a comment?\n        else if (match[0] === \"/*\") {\n          state.push(4);\n        }\n        // ending a [ ] or ( ) pair?\n        else if (/^[\\])]$/.test(match[0]) && state.length > 0) {\n          state.pop();\n        }\n        // handling whitespace or a combinator?\n        else if (/^(?:\\s+|[~+>])$/.test(match[0])) {\n          // need to insert whitespace before?\n          if (\n            tokens.length > 0 &&\n            !whitespace_pattern.test(tokens[tokens.length - 1]) &&\n            state[state.length - 1] === 0\n          ) {\n            // add normalized whitespace\n            tokens.push(\" \");\n          }\n\n          // case-insensitive attribute selector CSS L4\n          if (\n            state[state.length - 1] === 1 &&\n            tokens.length === 5 &&\n            tokens[2].charAt(tokens[2].length - 1) === \"=\"\n          ) {\n            tokens[4] = \" \" + tokens[4];\n          }\n\n          // whitespace token we can skip?\n          if (whitespace_pattern.test(match[0])) {\n            continue;\n          }\n        }\n\n        // save matched text\n        tokens.push(match[0]);\n      }\n      // otherwise, string literal or comment\n      else {\n        // save unmatched text\n        tokens[tokens.length - 1] += unmatched;\n\n        // unescaped terminator to string literal or comment?\n        if (not_escaped_pattern.test(tokens[tokens.length - 1])) {\n          // comment terminator?\n          if (state[state.length - 1] === 4) {\n            // ok to drop comment?\n            if (\n              tokens.length < 2 ||\n              whitespace_pattern.test(tokens[tokens.length - 2])\n            ) {\n              tokens.pop();\n            }\n            // otherwise, turn comment into whitespace\n            else {\n              tokens[tokens.length - 1] = \" \";\n            }\n\n            // handled already\n            match[0] = \"\";\n          }\n\n          state.pop();\n        }\n\n        // append matched text to existing token\n        tokens[tokens.length - 1] += match[0];\n      }\n    }\n    // otherwise, end of processing (no more matches)\n    else {\n      unmatched = sel.substr(next_match_idx);\n      saveUnmatched();\n\n      break;\n    }\n  }\n\n  return tokens.join(\"\").trim();\n}\n"
  },
  {
    "path": "src/querySelectorDeep.js",
    "content": "import { normalizeSelector } from './normalize';\n\n/**\n* Finds first matching elements on the page that may be in a shadow root using a complex selector of n-depth\n*\n* Don't have to specify all shadow roots to button, tree is travered to find the correct element\n*\n* Example querySelectorAllDeep('downloads-item:nth-child(4) #remove');\n*\n* Example should work on chrome://downloads outputting the remove button inside of a download card component\n*\n* Example find first active download link element querySelectorDeep('#downloads-list .is-active a[href^=\"https://\"]');\n*\n* Another example querySelectorAllDeep('#downloads-list div#title-area + a');\ne.g.\n*/\nexport function querySelectorAllDeep(selector, root = document, allElements = null) {\n    return _querySelectorDeep(selector, true, root, allElements);\n}\n\nexport function querySelectorDeep(selector, root = document, allElements = null) {\n    return _querySelectorDeep(selector, false, root, allElements);\n}\n\nfunction _querySelectorDeep(selector, findMany, root, allElements = null) {\n    selector = normalizeSelector(selector);\n    let lightElement = root.querySelector(selector);\n\n    if (document.head.createShadowRoot || document.head.attachShadow) {\n        // no need to do any special if selector matches something specific in light-dom\n        if (!findMany && lightElement) {\n            return lightElement;\n        }\n\n        // split on commas because those are a logical divide in the operation\n        const selectionsToMake = splitByCharacterUnlessQuoted(selector, ',');\n\n        return selectionsToMake.reduce((acc, minimalSelector) => {\n            // if not finding many just reduce the first match\n            if (!findMany && acc) {\n                return acc;\n            }\n            // do best to support complex selectors and split the query\n            const splitSelector = splitByCharacterUnlessQuoted(minimalSelector\n                    //remove white space at start of selector\n                    .replace(/^\\s+/g, '')\n                    .replace(/\\s*([>+~]+)\\s*/g, '$1'), ' ')\n                    // filter out entry white selectors\n                    .filter((entry) => !!entry)\n                    // convert \"a > b\" to [\"a\", \"b\"]\n                    .map((entry) => splitByCharacterUnlessQuoted(entry, '>'));\n\n            const possibleElementsIndex = splitSelector.length - 1;\n            const lastSplitPart = splitSelector[possibleElementsIndex][splitSelector[possibleElementsIndex].length - 1];\n            const possibleElements = collectAllElementsDeep(lastSplitPart, root, allElements);\n            const findElements = findMatchingElement(splitSelector, possibleElementsIndex, root);\n            if (findMany) {\n                acc = acc.concat(possibleElements.filter(findElements));\n                return acc;\n            } else {\n                acc = possibleElements.find(findElements);\n                return acc || null;\n            }\n        }, findMany ? [] : null);\n\n\n    } else {\n        if (!findMany) {\n            return lightElement;\n        } else {\n            return root.querySelectorAll(selector);\n        }\n    }\n\n}\n\nfunction findMatchingElement(splitSelector, possibleElementsIndex, root) {\n    return (element) => {\n        let position = possibleElementsIndex;\n        let parent = element;\n        let foundElement = false;\n        while (parent && !isDocumentNode(parent)) {\n            let foundMatch = true;\n            if (splitSelector[position].length === 1) {\n                foundMatch = parent.matches(splitSelector[position]);\n            } else {\n                // selector is in the format \"a > b\"\n                // make sure a few parents match in order\n                const reversedParts = ([]).concat(splitSelector[position]).reverse();\n                let newParent = parent;\n                for (const part of reversedParts) {\n                    if (!newParent || !newParent.matches(part)) {\n                        foundMatch = false;\n                        break;\n                    }\n                    newParent = findParentOrHost(newParent, root);\n                }\n            }\n\n            if (foundMatch && position === 0) {\n                foundElement = true;\n                break;\n            }\n            if (foundMatch) {\n                position--;\n            }\n            parent = findParentOrHost(parent, root);\n        }\n        return foundElement;\n    };\n\n}\n\nfunction splitByCharacterUnlessQuoted(selector, character) {\n    return selector.match(/\\\\?.|^$/g).reduce((p, c) => {\n        if (c === '\"' && !p.sQuote) {\n            p.quote ^= 1;\n            p.a[p.a.length - 1] += c;\n        } else if (c === '\\'' && !p.quote) {\n            p.sQuote ^= 1;\n            p.a[p.a.length - 1] += c;\n\n        } else if (!p.quote && !p.sQuote && c === character) {\n            p.a.push('');\n        } else {\n            p.a[p.a.length - 1] += c;\n        }\n        return p;\n    }, { a: [''] }).a;\n}\n\n/**\n * Checks if the node is a document node or not.\n * @param {Node} node\n * @returns {node is Document | DocumentFragment}\n */\nfunction isDocumentNode(node) {\n    return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.DOCUMENT_NODE;\n}\n\nfunction findParentOrHost(element, root) {\n    const parentNode = element.parentNode;\n    return (parentNode && parentNode.host && parentNode.nodeType === 11) ? parentNode.host : parentNode === root ? null : parentNode;\n}\n\n/**\n * Finds all elements on the page, inclusive of those within shadow roots.\n * @param {string=} selector Simple selector to filter the elements by. e.g. 'a', 'div.main'\n * @return {!Array<string>} List of anchor hrefs.\n * @author ebidel@ (Eric Bidelman)\n * License Apache-2.0\n */\nexport function collectAllElementsDeep(selector = null, root, cachedElements = null) {\n    let allElements = [];\n\n    if (cachedElements) {\n        allElements = cachedElements;\n    } else {\n        const findAllElements = function(nodes) {\n            for (let i = 0; i < nodes.length; i++) {\n                const el = nodes[i];\n                allElements.push(el);\n                // If the element has a shadow root, dig deeper.\n                if (el.shadowRoot) {\n                    findAllElements(el.shadowRoot.querySelectorAll('*'));\n                }\n            }\n        };\n        if(root.shadowRoot) {\n            findAllElements(root.shadowRoot.querySelectorAll('*'));\n        }\n        findAllElements(root.querySelectorAll('*'));\n    }\n\n    return selector ? allElements.filter(el => el.matches(selector)) : allElements;\t}\n\n"
  },
  {
    "path": "steps.d.ts",
    "content": "/// <reference types='codeceptjs' />\ntype steps_file = typeof import('./steps_file.js');\n\ndeclare namespace CodeceptJS {\n  interface SupportObject { I: CodeceptJS.I }\n  interface CallbackOrder { [0]: CodeceptJS.I }\n  interface Methods extends CodeceptJS.Playwright {}\n  interface I extends ReturnType<steps_file> {}\n  namespace Translation {\n    interface Actions {}\n  }\n}\n"
  },
  {
    "path": "steps_file.js",
    "content": "// in this file you can append custom step methods to 'I' object\n\nmodule.exports = function() {\n  return actor({\n\n    // Define custom steps here, use 'this' to access default methods of I.\n    // It is recommended to place a general 'login' function here.\n\n  });\n}\n"
  },
  {
    "path": "test/TestComponent.js",
    "content": "export class TestComponent extends HTMLElement {\n\n    constructor({ childClassName = 'test-child', childTextContent = 'Child Content', internalHTML = '' } = {}) {\n        super();\n        this.childClassName = childClassName;\n        this.childTextContent = childTextContent;\n        this.internalHTML = internalHTML;\n        this.style.display = 'block';\n        this.style.margin = '5px';\n    }\n\n    connectedCallback() {\n        this.attachShadow({ mode: 'open' });\n        this.shadowRoot.innerHTML = `<p class=\"${this.childClassName}\">${this.childTextContent}</p><div>${this.internalHTML}</div><slot></slot>`;\n    }\n\n    add(child) {\n        this.shadowRoot.appendChild(child);\n    }\n\n    addNested(child) {\n        this.shadowRoot.querySelector('p').appendChild(child);\n    }\n\n    static get observedAttributes() {\n        return ['child-class-name', 'child-text-content', 'internal-html'];\n    }\n\n    attributeChangedCallback(name, oldValue, newValue) {\n        switch (name) {\n            case 'child-class-name':\n                this.childClassName = newValue;\n                break;\n            case 'child-text-content':\n                this.childTextContent = newValue;\n                break;\n            case 'internal-html':\n                this.internalHTML = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('test-component', TestComponent);"
  },
  {
    "path": "test/basic.spec.js",
    "content": "import { querySelectorAllDeep, querySelectorDeep, collectAllElementsDeep } from '../src/querySelectorDeep.js';\nimport { createTestComponent, createNestedComponent, COMPONENT_NAME, createChildElements } from './createTestComponent.js';\n\n\n\ndescribe(\"Basic Suite\", function() {\n\n    let parent;\n    let baseComponent;\n\n    function setup() {\n        parent = document.createElement('div');\n        document.body.appendChild(parent);\n        baseComponent = createTestComponent(parent, {\n            childClassName: 'base',\n            childTextContent: 'Base Component'\n        });\n        baseComponent.classList.add('base-comp');\n    }\n    beforeEach(() => {\n        setup();\n    });\n\n    afterEach(() => {\n        parent.remove();\n    });\n\n    it(\"exports querySelectorAllDeep function\", function() {\n        expect(querySelectorAllDeep).toEqual(jasmine.any(Function));\n    });\n\n    it(\"exports querySelectorDeep function\", function() {\n        expect(querySelectorDeep).toEqual(jasmine.any(Function));\n    });\n\n    it(\"exports collectAllElementsDeep function\", function() {\n        expect(collectAllElementsDeep).toEqual(jasmine.any(Function));\n    });\n\n    it(\"querySelectorDeep returns null when not found\", function() {\n        expect(querySelectorDeep('whatever')).toBeNull();\n    });\n\n    it(\"querySelectorAllDeep returns empty array when not found\", function() {\n        const foundElements = querySelectorAllDeep('whatever');\n        expect(foundElements).toEqual(jasmine.any(Array));\n        expect(foundElements.length).toEqual(0);\n    });\n\n\n    describe(\"querySelectorDeep\", function() {\n        it('can access an element in the light dom', function() {\n            createTestComponent(parent);\n            const testComponent = querySelectorDeep(COMPONENT_NAME);\n            expect(testComponent).toBeTruthy();\n        });\n\n        it('can access direct shadow dom child of the root', function() {\n            const rootComponent = createTestComponent(parent, {\n                childClassName: \"child-class\",\n            });\n            const child = querySelectorDeep('.child-class', rootComponent);\n            expect(child).toBeTruthy();\n        });\n\n\n        it('can access an element in the shadow dom', function() {\n            createTestComponent(parent, {\n                childTextContent: 'Child Content'\n            });\n            const testSubComponent = querySelectorDeep('.test-child');\n            expect(testSubComponent).toBeTruthy();\n            expect(testSubComponent.textContent).toEqual('Child Content')\n        });\n\n        it('can access an element nested in many shadow dom', function() {\n            createNestedComponent(baseComponent, 20);\n            const testSubComponent = querySelectorDeep('.desc-20');\n            expect(testSubComponent).toBeTruthy();\n            expect(testSubComponent.textContent).toEqual('Descendant 20')\n        });\n\n\n        it('can use find the nth-child inside of a shadow root', function() {\n            createNestedComponent(baseComponent, 10, {\n                createChildren: createChildElements\n            });\n            const testSubComponent = querySelectorDeep('.desc-5 div:nth-child(2)');\n            expect(testSubComponent).toBeTruthy();\n            expect(testSubComponent.textContent).toEqual('Child 2')\n        });\n\n        it('selector list only returns the first element', function() {\n            createNestedComponent(baseComponent, 10, {\n                createChildren: createChildElements\n            });\n            const testSubComponent = querySelectorDeep('.desc-5 div:nth-child(2), .desc-1');\n            expect(testSubComponent).toBeTruthy();\n            expect(testSubComponent.textContent).toEqual('Child 2')\n        });\n\n        it('returns null when reaching the document node and no matching parent was found', function() {\n            const rootComponent = createTestComponent(parent, {\n                childClassName: 'child',\n            })\n            const testSubComponent = querySelectorDeep('.parent .child', rootComponent);\n            expect(testSubComponent).toBeNull();\n        });\n    });\n\n\n    describe(\"querySelectorAll\", function() {\n\n        it('can locate all instances of components across even in shadow dom (except base)', function() {\n            createNestedComponent(baseComponent, 10);\n            const testComponents = querySelectorAllDeep(`test-component:not(.base-comp)`);\n            expect(testComponents.length).toEqual(10);\n\n        });\n\n        it('can get elements matching or selector', function() {\n            createTestComponent(parent, {\n                childClassName: 'header-1'\n            });\n            const element = createTestComponent(parent, {\n                childClassName: 'header-2'\n            });\n            element.classList.add('parent')\n            const testComponents = querySelectorAllDeep(`.header-1, .header-2, .parent`);\n            expect(testComponents.length).toEqual(3);\n\n        });\n\n        it('host property on non shadowRoot element is ignored', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\"><div class=\"find-me\"></div></div>'\n            });\n            testComponent.shadowRoot.querySelector('.header-2').host = \"test.com\";\n            testComponent.classList.add('container');\n            const testComponents = querySelectorAllDeep(`.container .find-me`);\n            expect(testComponents.length).toEqual(1);\n        });\n\n        it('can see inside the shadowRoot with \">\" in selector', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\"><div class=\"find-me\"></div></div>'\n            });\n            testComponent.shadowRoot.querySelector('.header-2').host = \"test.com\";\n            testComponent.classList.add('container');\n            const testComponents = querySelectorAllDeep(`.container > div > .header-2 > .find-me`);\n            expect(testComponents.length).toEqual(1);\n            expect(testComponents[0].classList.contains('find-me')).toEqual(true);\n        });\n\n        it('handles descendant selector > that dooes not match child', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\"><div class=\"find-me\"></div></div>'\n            });\n            testComponent.shadowRoot.querySelector('.header-2').host = \"test.com\";\n            testComponent.classList.add('container');\n            const testComponents = querySelectorAllDeep(`.container > div > .header-2 > .doesnt-exist`);\n            expect(testComponents.length).toEqual(0);\n        });\n\n        it('handles descendant selector where child exists but parent does not', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\"><div class=\"find-me\"></div></div>'\n            });\n            testComponent.shadowRoot.querySelector('.header-2').host = \"test.com\";\n            testComponent.classList.add('container');\n            const testComponents = querySelectorAllDeep(`.container > div > .doesnt-exist > .find-me`);\n            expect(testComponents.length).toEqual(0);\n        });\n\n\n        it('can handle extra white space in selectors', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1      .header-2`);\n            expect(testComponents.length).toEqual(2);\n\n        });\n\n        it('can handle attribute selector value', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div data-test=\"Hello-World\" class=\"header-2\">Content</div>'\n            });\n            createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            testComponent.setAttribute('data-test', '123')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [data-test=\"Hello-World\"]`);\n            expect(testComponents.length).toEqual(1);\n            expect(testComponents[0].classList.contains('header-2')).toBeTruthy();\n        });\n\n\n        it('can handle extra white space in attribute value', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div data-test=\"Hello World\" class=\"header-2\">Content</div>'\n            });\n            createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            // this should not match as matching children\n            testComponent.setAttribute('data-test', 'Hello World')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [data-test=\"Hello World\"]`);\n            expect(testComponents.length).toEqual(1);\n        });\n\n\n        it('can handle comma in attribute values', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            const test2 = createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            test2.setAttribute('data-test', 'Hello, World')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [data-test=\"Hello, World\"], .header-2, .header-1`);\n            expect(testComponents.length).toEqual(5);\n        });\n\n        it('can handle spacing around attribute values', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            const test2 = createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            test2.setAttribute('data-test', 'Hello, World')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [ data-test = \"Hello, World\" ], .header-2, .header-1`);\n            expect(testComponents.length).toEqual(5);\n        });\n\n        it('can handle spacing around attribute values with [ in attribute', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            const test2 = createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            test2.setAttribute('data-braces-test', ' [ Hello, World ] ')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [ data-braces-test = \" [ Hello, World ] \" ], .header-2, .header-1`);\n            expect(testComponents.length).toEqual(5);\n        });\n\n        it('can escaped comma in attribute values', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            const test2 = createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            test2.setAttribute('data-test', 'Hello\\, World')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [data-test=\"Hello\\, World\"]`);\n            expect(testComponents.length).toEqual(1);\n        });\n\n\n        it('can handle escaped data in attributes', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            const test2 = createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            test2.setAttribute('data-test', 'Hello\" World')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1 [data-test=\"Hello\\\\\" World\"]`);\n            expect(testComponents.length).toEqual(1);\n        });\n\n        it('can handle extra white space in single quoted attribute value', function() {\n            const testComponent = createTestComponent(parent, {\n                childClassName: 'header-1',\n                internalHTML: '<div class=\"header-2\">Content</div>'\n            });\n            createTestComponent(testComponent, {\n                childClassName: 'header-2'\n            });\n            testComponent.setAttribute('data-test', 'Hello \" \\'World\\'')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1[data-test='Hello \\\\\" \\\\'World\\\\'']`);\n            expect(testComponents.length).toEqual(1);\n        });\n\n        it('split correctly on selector list', function() {\n            const testComponent = createTestComponent(parent, {\n                internalHTML: '<span class=\"header-2\"></span><div data-test=\"Hello\" World\" class=\"header-3\">Content</div>'\n            });\n            createTestComponent(testComponent, {\n                childClassName: 'header-4'\n            });\n            testComponent.setAttribute('data-test', '123')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`.header-1,.header-2 + .header-3`);\n            expect(testComponents.length).toEqual(2);\n            expect(testComponents[1].classList.contains('header-3')).toBeTruthy();\n        });\n\n        it('split correctly on selector list (ignore white space)', function() {\n            const testComponent = createTestComponent(parent, {\n                internalHTML: '<span class=\"header-2\"></span><div data-test=\"Hello World\" class=\"header-3\">Content</div>'\n            });\n            createTestComponent(testComponent, {\n                childClassName: 'header-4'\n            });\n            testComponent.setAttribute('data-test', '123')\n            testComponent.classList.add('header-1');\n            const testComponents = querySelectorAllDeep(`    .header-1,     .header-2 + .header-3`);\n            expect(testComponents.length).toEqual(2);\n            expect(testComponents[1].classList.contains('header-3')).toBeTruthy();\n        });\n\n        it('can provide an alternative node', function() {\n            const root = document.createElement('div');\n            parent.appendChild(root);\n\n            createTestComponent(root, {\n                childClassName: 'inner-content'\n            });\n\n            createTestComponent(parent, {\n                childClassName: 'inner-content'\n            });\n            const testComponent = querySelectorAllDeep('.inner-content', root);\n            expect(testComponent.length).toEqual(1);\n\n        });\n\n        it('can cache collected elements with collectAllElementsDeep', function() {\n            const root = document.createElement('div');\n            parent.appendChild(root);\n\n            createTestComponent(root, {\n                childClassName: 'inner-content'\n            });\n\n            createTestComponent(parent, {\n                childClassName: 'inner-content'\n            });\n            const collectedElements = collectAllElementsDeep('*', root)\n            expect(collectedElements.length).toEqual(4);\n\n            const testComponents = querySelectorAllDeep('.inner-content', root, collectedElements);\n            expect(testComponents.length).toEqual(1);\n\n            // remove element from dom\n            testComponents[0].remove()\n\n            // not found in dom\n            const testComponents2 = querySelectorAllDeep('.inner-content', root);\n            expect(testComponents2.length).toEqual(0);\n\n            // still there with cached collectedElements\n            const testComponents3 = querySelectorAllDeep('.inner-content', root, collectedElements);\n            expect(testComponents3.length).toEqual(1);\n        });\n\n        it('empty collectAllElementsDeep find all elements', function() {\n            const root = document.createElement('div');\n            parent.appendChild(root);\n\n            createTestComponent(root, {\n                childClassName: 'inner-content'\n            });\n\n            createTestComponent(parent, {\n                childClassName: 'inner-content'\n            });\n            const collectedElements = collectAllElementsDeep('', root);\n            expect(collectedElements.length).toEqual(4);\n        });\n\n        it('can query nodes in an iframe', function(done) {\n\n            const innerframe = `<p class='child'>Content</p>`;\n            createTestComponent(parent, {\n                internalHTML: `<iframe id=\"frame\" srcdoc=\"${innerframe}\"></iframe>`\n            });\n            setTimeout(() => {\n                const iframe = querySelectorDeep('#frame');\n                const testComponents = querySelectorAllDeep('.child', iframe.contentDocument);\n                expect(testComponents.length).toEqual(1);\n                expect(testComponents[0].textContent).toEqual(\"Content\");\n                done();\n            }, 150);\n\n\n        });\n\n\n\n        // describe(\".perf\", function() {\n\n        //     function generateQuerySelectorAllTest(count) {\n        //         it(`can create ${count} shadow roots and search for all instances that match`, async function() {\n        //             for (let i = 0; i < count; i++) {\n        //                 createTestComponent(baseComponent, count);\n        //             }\n        //             const testComponents = querySelectorAllDeep('test-component [class=test-child]');\n        //             expect(testComponents.length).toEqual(count);\n        //         });\n        //     }\n\n        //     generateQuerySelectorAllTest(200000);\n\n        // });\n\n    });\n\n\n});\n"
  },
  {
    "path": "test/codeceptjs/README.md",
    "content": "CodeceptJS functions to test:\n\nappendField\nattachFile\ncheckOption\nclearField\nclick\nclickLin\ndontSee\ndontSeeCheckboxIsChecked\ndontSeeCookie\ndontSeeCurrentUrlEquals\ndontSeeElement\ndontSeeElementInDOM\ndontSeeInField\ndoubleClick\ndragAndDrop\ndragSlider\nfillField\nforceClick\ngrabAttributeFrom\ngrabCssPropertyFrom\ngrabDataFromPerformanceTiming\ngrabElementBoundingRect\ngrabHTMLFrom\ngrabNumberOfVisibleElements\ngrabPageScrollPosition\ngrabTextFrom\ngrabValueFrom\nmoveCursorTo\nopenNewTab\npressKey\npressKeyDown\npressKeyUp\nrightClick\nscrollPageToBottom\nscrollPageToTop\nscrollTo\nsee\nseeAttributesOnElements\nseeCheckboxIsChecked\nseeCssPropertiesOnElements\nseeElement\nseeElementInDO\nseeInField\nseeNumberOfElements\nseeNumberOfVisibleElements\nseeTextEquals\nselectOptio\nuncheckOptio\nwaitForClickable\nwaitForDetached\nwaitForElement\nwaitForEnabled\nwaitForInvisible\nwaitForText\nwaitForValue\nwaitForVisible\nwaitNumberOfVisibleElements\nwaitToHide\nwaitUntil"
  },
  {
    "path": "test/codeceptjs/codecept.conf.js",
    "content": "exports.config = {\n  tests: 'test/codeceptjs/*.test.js',\n  output: './output',\n  helpers: {\n    Playwright: {\n      url: 'http://localhost',\n      show: true,\n      browser: 'chromium'\n    }\n  },\n  include: {\n    I: './steps_file.js'\n  },\n  bootstrap: null,\n  mocha: {},\n  name: 'codeceptjs',\n  plugins: {\n    retryFailedStep: {\n      enabled: true\n    },\n    screenshotOnFail: {\n      enabled: true\n    }\n  }\n}"
  },
  {
    "path": "test/codeceptjs/components.test.js",
    "content": "Feature('components');\n\nScenario('test something', (I) => {\n\n});\n"
  },
  {
    "path": "test/codeceptjs/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true\n  }\n}"
  },
  {
    "path": "test/codeceptjs/steps.d.ts",
    "content": "/// <reference types='codeceptjs' />\ntype steps_file = typeof import('./steps_file.js');\n\ndeclare namespace CodeceptJS {\n  interface SupportObject { I: CodeceptJS.I }\n  interface CallbackOrder { [0]: CodeceptJS.I }\n  interface Methods extends CodeceptJS.Playwright {}\n  interface I extends ReturnType<steps_file> {}\n  namespace Translation {\n    interface Actions {}\n  }\n}\n"
  },
  {
    "path": "test/codeceptjs/steps_file.js",
    "content": "// in this file you can append custom step methods to 'I' object\n\nmodule.exports = function() {\n  return actor({\n\n    // Define custom steps here, use 'this' to access default methods of I.\n    // It is recommended to place a general 'login' function here.\n\n  });\n}\n"
  },
  {
    "path": "test/createTestComponent.js",
    "content": "import { TestComponent } from './TestComponent.js';\nexport function createTestComponent(parent, options = {}) {\n    return parent.appendChild(new TestComponent(options));\n}\n\nexport const createChildElements = (component) => {\n    for (let i = 0; i < 3; i++) {\n        const child = document.createElement('div');\n        child.textContent = `Child ${i+1}`;\n        component.addNested(child);\n    }\n};\n\nexport function createNestedComponent(parent, count = 1, { createChildren = () => {}, childClass = (count) => `desc-${count}`, childContent = (count) => `Descendant ${count}` } = {}) {\n    if (count > 2000) {\n        const split = count / 2;\n        for (let i = 0; i < count; i += split) {\n            createNestedComponent(parent, split, {\n                createChildren,\n                childClass,\n                childContent\n            });\n        }\n    } else {\n        if (count === 0) {\n            return;\n        }\n        const component = new TestComponent({\n            childClassName: childClass(count),\n            childTextContent: childContent(count)\n        });\n        parent.add(component);\n        createChildren(component);\n        count = count - 1;\n        createNestedComponent(component, count, { createChildren, childClass, childContent });\n    }\n\n\n\n}\n\nexport const COMPONENT_NAME = 'test-component';"
  },
  {
    "path": "test/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Testing</title>\n</head>\n\n<body>\n    <script>\n        (async () => {\n            const {\n                createTestComponent,\n                createNestedComponent,\n                createChildElements\n            } = await\n                    import('./createTestComponent.js');\n            const {\n                querySelectorAllDeep,\n                querySelectorDeep\n            } = await\n                    import('../src/querySelectorDeep.js');\n            const parent = document.createElement('div');\n            document.body.appendChild(parent);\n            const innerframe = `<p class='child'>Content</p>`;\n            createTestComponent(parent, {\n                internalHTML: `<button class=\"btn-in-shadow-dom\">Shadow dom button</button><span class=\"output\">Not clicked</span>`\n            });\n\n            createTestComponent(parent, {\n                internalHTML: `<input id=\"type-to-input\" type=\"text\"/>`\n            });\n\n            querySelectorDeep(\".btn-in-shadow-dom\").onclick = (e) => querySelectorDeep(\".output\", e.target.parentElement).innerText = \"clicked the btn\"\n            setTimeout(() => {\n                const iframe = querySelectorDeep('#frame');\n                const testComponents = querySelectorAllDeep('.child', iframe.contentDocument);\n                expect(testComponents.length).toEqual(1);\n                expect(testComponents[0].textContent).toEqual(\"Content\");\n                done();\n            }, 300);\n        })();\n    </script>\n</body>\n\n</html>"
  },
  {
    "path": "test/nopolyfills.spec.js",
    "content": "import { querySelectorAllDeep, querySelectorDeep } from '../src/querySelectorDeep.js';\n\ndescribe(\"No Polyfills Suite\", function() {\n\n    let parent;\n\n    function setup() {\n        parent = document.createElement('div');\n        document.body.appendChild(parent);\n    }\n    beforeEach(() => {\n        spyOnProperty(document, 'head', 'get').and.returnValue({\n            attachShadow: undefined,\n            createShadowRoot: undefined\n        });\n        setup();\n    });\n\n    afterEach(() => {\n        parent.remove();\n    });\n\n\n    it('can fallback to query selector when no support and no polyfills', function() {\n        const element = document.createElement('a');\n        element.classList.add('testing');\n        parent.appendChild(element);\n        expect(querySelectorDeep('.testing')).toBeTruthy();\n    });\n\n    it('can fallback to query selector all when no support and no polyfills', function() {\n        const element = document.createElement('a');\n        const element2 = document.createElement('a');\n        element.classList.add('testing');\n        element2.classList.add('testing');\n        parent.appendChild(element);\n        parent.appendChild(element2);\n        expect(querySelectorAllDeep('.testing').length).toEqual(2);\n    });\n\n    it('can fallback to query selector when no support and no polyfills with alternative root', function() {\n        const root = document.createElement('div');\n        parent.appendChild(root);\n        const element = document.createElement('a');\n        const element2 = document.createElement('a');\n        element.classList.add('testing');\n        element2.classList.add('testing');\n        root.appendChild(element);\n        parent.appendChild(element2);\n\n        expect(querySelectorAllDeep('.testing', root).length).toEqual(1);\n    });\n\n\n\n});"
  },
  {
    "path": "test/protractor-locator.e2e.js",
    "content": "describe('query-selector-shadow-dom', () => {\n\n    beforeEach(async () => {\n        await browser.get(pageFromTemplate(`\n            var firstParent = document.createElement('div');\n            firstParent.id = 'first';\n            firstParent.classList.add('parent');            \n            document.body.appendChild(firstParent);\n            \n            var secondParent = document.createElement('div');\n            secondParent.classList.add('parent');            \n            document.body.appendChild(secondParent);\n            \n            var firstShadowChild = firstParent.attachShadow({ mode: 'open' });\n            firstShadowChild.innerHTML = '<p class=\"shadow-child\"><span class=\"name\">First child</span> with Shadow DOM</p>';        \n            \n            var secondShadowChild = secondParent.attachShadow({ mode: 'open' });\n            secondShadowChild.innerHTML = '<p class=\"shadow-child\"><span class=\"name\">Second child</span> with Shadow DOM</p>';\n        `));\n    });\n\n    it(`identifies a single element matching the selector`, async () => {\n        const text = await element(by.shadowDomCss('#first.parent .shadow-child .name')).getText();\n\n        expect(text).toEqual('First child');\n    });\n\n    it(`identifies all elements matching the selector`, async () => {\n        const text = await element.all(by.shadowDomCss('.parent .shadow-child .name')).getText();\n\n        expect(text).toEqual(['First child', 'Second child']);\n    });\n});\n\n/**\n * Turns a HTML template into a data URL Protractor can navigate to without having to use a web server\n *\n * @param {string} template\n * @returns {string}\n */\nfunction pageFromTemplate(template /* string */) /* string */ {\n    return `data:text/html;charset=utf-8,<!DOCTYPE html>\n        <html>\n            <body>\n                <script>${ template }</script>\n            </body>\n        </html>`.replace(/[\\s\\n]+/s, ' ');\n}\n"
  }
]