diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml new file mode 100644 index 00000000..64a9cab3 --- /dev/null +++ b/.github/workflows/playwright.yaml @@ -0,0 +1,58 @@ +on: pull_request +name: Playwright Tests +jobs: + playwright: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install site + run: | + docker network create frontend + docker network create serviceplatformen_organisation_api_app + docker compose pull + docker compose up --detach + # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. + docker compose exec --user root phpfpm composer install --no-interaction + # Install the site + docker compose exec --user root phpfpm vendor/bin/drush site:install --existing-config --yes + # Download and install external libraries + docker compose exec --user root phpfpm vendor/bin/drush webform:libraries:download + # Build theme assets + docker compose run --rm node yarn --cwd /app/web/themes/custom/os2forms_selvbetjening_theme install + docker compose run --rm node yarn --cwd /app/web/themes/custom/os2forms_selvbetjening_theme build + # Open the site + echo $(docker compose exec phpfpm vendor/bin/drush --uri=http://$(docker compose port nginx 8080) user:login) + + + - name: Install dependencies + run: yarn install + + - name: Install Playwright Browsers + run: docker compose run --rm playwright yarn playwright install --with-deps + + - name: Run Playwright tests + run: | + docker compose run --rm playwright yarn playwright test --update-snapshots --retries 3 | tee output.log + if grep -q -e "Error: A snapshot doesn't exist at" -e "Screenshot comparison failed" output.log; then + echo "Playwright tests failed due to a snapshot issue." + echo "SNAPSHOT_DIFFERENCES=true" >> $GITHUB_ENV + exit 1 + elif grep -q "failed" output.log; then + echo "Playwright tests failed due to a non-snapshot issue." + exit 1 + fi + - uses: actions/upload-artifact@v4 + id: artifact-upload + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + - name: Comment on PR with report link + uses: thollander/actions-comment-pull-request@v3 + if: ${{ failure() && env.SNAPSHOT_DIFFERENCES == 'true' }} + with: + message: | + ### Playwright visual snapshot differences were detected. + View the [Playwright report](${{ steps.artifact-upload.outputs.artifact-url }}) to review the visual differences. + **To approve the snapshot changes and update the snapshots, please comment:** /approve-snapshots diff --git a/.gitignore b/.gitignore index 5299ff6a..b2b489a1 100644 --- a/.gitignore +++ b/.gitignore @@ -230,3 +230,9 @@ config/sync/**/leaflet_layers.map_layer.* config/sync/os2web_nemlogin.settings.yml # Ignore attachment components config/sync/os2forms_attachment.os2forms_attachment_component.*.yml + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 67371e1c..9ceecfc4 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -5,6 +5,9 @@ networks: external: true name: serviceplatformen_organisation_api_app +include: + - docker-compose.playwright.yml + services: phpfpm: networks: @@ -13,7 +16,7 @@ services: - PHP_MEMORY_LIMIT=512M node: - image: node:16 + image: node:22 profiles: - dev networks: diff --git a/docker-compose.playwright.yml b/docker-compose.playwright.yml new file mode 100644 index 00000000..eb2965ef --- /dev/null +++ b/docker-compose.playwright.yml @@ -0,0 +1,15 @@ +services: + playwright: + # https://playwright.dev/docs/docker + # This Playwright version should match the one in `package.json`. + image: mcr.microsoft.com/playwright:v1.51.1 + profiles: + - test + networks: + - app + depends_on: + - nginx + volumes: + - .:/app + - /tmp/.X11-unix:/tmp/.X11-unix + working_dir: /app diff --git a/package.json b/package.json index 50049294..6d7d5bda 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "license": "UNLICENSED", "private": true, "devDependencies": { + "@playwright/test": "^1.51.1", + "@types/node": "^22.13.9", "markdownlint-cli": "^0.31.1" }, "scripts": { diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000..9ada4f56 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,87 @@ +// @ts-check +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './playwright', + /* Run tests in files in parallel */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://nginx:8080', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'hest', + testMatch: /global\.setup\.js/, + }, + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + dependencies: ['hest'], + }, +/* + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + dependencies: ['hest'], + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + dependencies: ['hest'], + },*/ + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/playwright/example.spec.js b/playwright/example.spec.js new file mode 100644 index 00000000..1a4d016f --- /dev/null +++ b/playwright/example.spec.js @@ -0,0 +1,54 @@ +// @ts-check +import { test, expect } from '@playwright/test'; +import { SetupWebform } from './fixtures/SetupWebform'; +import { AddElementToWebform } from './fixtures/AddElementToWebform'; + +const runnerTimestamp = new Date().getTime(); + +test.describe('Login flow, create webform', () => { + let authenticatedContext; + + test.beforeAll('1. Login', async ({ browser }) => { + + const context = await browser.newContext(); + const page = await context.newPage(); + + await page.goto('/da/user/login'); + + await expect(page.locator('input[name="name"]')).toBeVisible(); + await page.fill('input[name="name"]', 'playwright'); + await page.fill('input[name="pass"]', 'playwrightt3st01'); + + await page.click('input[name="op"]'); + + await expect(page.locator('#block-gin-page-title > h1')).toHaveText('playwright'); + + authenticatedContext = context; + + await page.close(); + }); + + test('2. Create new webform', async () => { + const page = await authenticatedContext.newPage(); + const webform = new SetupWebform(page, runnerTimestamp); + + await webform.goto(); + await webform.openCreateNewWebformDialog(); + await webform.createNewWebform(); + }) + + test('3. Configure textfield element', async ({} , testInfo) => { + const page = await authenticatedContext.newPage(); + const addElementToWebform = new AddElementToWebform(page, runnerTimestamp); + + await addElementToWebform.addElement('textfield'); + + const customersScreenshot = await page.screenshot(); + await testInfo.attach('Customers Page', { + body: customersScreenshot, + contentType: 'image/png', + }); + + }) + +}); diff --git a/playwright/fixtures/AddElementToWebform.js b/playwright/fixtures/AddElementToWebform.js new file mode 100644 index 00000000..87bd0410 --- /dev/null +++ b/playwright/fixtures/AddElementToWebform.js @@ -0,0 +1,20 @@ +import { expect } from '@playwright/test'; +import { GlobalSettings } from './GlobalSettings'; + +export class AddElementToWebform { + constructor(page, runnerTimestamp) { + this.page = page; + this.webformName = GlobalSettings.webformName + runnerTimestamp; + this.titleInput = this.page.locator('div.js-form-item-properties-title > input[name="properties[title]"]'); + } + + async addElement(elementName) { + await this.page.goto(`da/admin/structure/webform/manage/${this.webformName}/element/add/${elementName}`); + await expect(this.page.locator('#block-gin-page-title > h1')).toHaveText('Tilføj Tekstfelt element'); + await this.titleInput.fill(elementName); + await this.page.click('div.form-actions > input.form-submit'); + + await expect(this.page.locator(`a#edit-webform-ui-elements-${elementName}-title-link`)).toBeVisible(); + } + +} diff --git a/playwright/fixtures/GlobalSettings.js b/playwright/fixtures/GlobalSettings.js new file mode 100644 index 00000000..286562f0 --- /dev/null +++ b/playwright/fixtures/GlobalSettings.js @@ -0,0 +1,4 @@ +export class GlobalSettings { + static affiliationName = 'playwright_affiliation'; + static webformName = 'playwright_webform'; +} diff --git a/playwright/fixtures/SetupWebform.js b/playwright/fixtures/SetupWebform.js new file mode 100644 index 00000000..ff6ed693 --- /dev/null +++ b/playwright/fixtures/SetupWebform.js @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; +import { GlobalSettings } from './GlobalSettings'; + +export class SetupWebform { + constructor(page, runnerTimestamp) { + this.page = page; + this.webformName = GlobalSettings.webformName + runnerTimestamp; + this.titleInput = this.page.locator('div.js-form-item-title > input[name="title"]'); + this.createWebformButton = this.page.locator('div.form-actions > button.form-submit'); + } + + async goto() { + await this.page.goto('/da/admin/structure/webform'); + await expect(this.page.locator('#block-gin-page-title > h1')).toHaveText('Webformularer'); + } + + async openCreateNewWebformDialog(title) { + await expect(this.page.locator('a[href="/da/admin/structure/webform/add"]')).toBeVisible(); + await this.page.click('a[href="/da/admin/structure/webform/add"]'); + await expect(this.page.locator('div[role="dialog"].ui-dialog')).toBeVisible(); + } + + async createNewWebform() { + await this.titleInput.fill(this.webformName); + await this.createWebformButton.click(); + await expect(this.page.locator('div#block-gin-page-title > h1')).toHaveText(this.webformName); + } +} diff --git a/playwright/global.setup.js b/playwright/global.setup.js new file mode 100644 index 00000000..f7c5aaa7 --- /dev/null +++ b/playwright/global.setup.js @@ -0,0 +1,65 @@ +// @ts-check +import { test as baseTest, expect } from '@playwright/test'; +import { GlobalSettings } from './fixtures/GlobalSettings'; + +const test = baseTest.extend({ + authenticatedContext: async ({ browser }, use) => { + // Create a new browser context + const context = await browser.newContext(); + const page = await context.newPage(); + const affiliation = GlobalSettings.affiliationName; + + try { + // Perform login + await page.goto('/da/user/login'); + await page.fill('input[name="name"]', 'playwright'); + await page.fill('input[name="pass"]', 'playwrightt3st01'); + await page.click('input[name="op"]'); + + // Ensure login is successful + await expect(page.locator('h1.page-title').filter({hasText: 'playwright'})).toBeVisible(); + console.log('Successfully logged in.'); + + // Navigate to overview and check if affiliation exists + await page.goto('da/admin/structure/taxonomy/manage/user_affiliation/overview'); + const affiliationExists = await page.locator('table#taxonomy a').filter({ hasText: affiliation }).isVisible(); + + // Create an affiliation if it does not exist + if (!affiliationExists) { + await page.goto('da/admin/structure/taxonomy/manage/user_affiliation/add'); + await page.fill('input[name="name[0][value]"]', affiliation); + await page.click('input[data-drupal-selector="edit-overview"]'); + + // Verify creation of affiliation + await expect(page.locator('table#taxonomy a').filter({ hasText: affiliation })).toBeVisible(); + console.log('Successfully created affiliation.'); + } else { + console.log('Affiliation already exists.'); + } + + // Assign affiliation to user + await page.goto('da/admin/people'); + await expect(page.locator('td.views-field-name > a').filter({hasText: 'playwright'})).toBeVisible(); + await expect(page.locator('td.views-field-name > a').filter({hasText: 'playwright'})).toHaveAttribute('href'); + const userUrl = await page.locator('td.views-field-name > a').filter({hasText: 'playwright'}).getAttribute('href'); + + await page.goto(userUrl + '/edit?destination=/da/admin/people'); + + await expect(page.locator('div.js-form-item-terms-user-affiliation > select#edit-terms-user-affiliation > option').filter({hasText: affiliation})).toBeDefined(); + await page.locator('div.js-form-item-terms-user-affiliation > select#edit-terms-user-affiliation > option').filter({hasText: affiliation}).click(); + await page.locator('div#edit-actions > input#edit-submit').click(); + + console.log('Successfully assigned affiliation.'); + // Provide the authenticated context to dependent tests + await use(context); + } finally { + console.log('Basic setup complete.'); + } + }, +}); + +test('Global setup - Create authenticated context', async ({ authenticatedContext }) => { + console.log('Authenticated context is ready for dependent tests.'); +}); + +module.exports = { test }; diff --git a/yarn.lock b/yarn.lock index cca4ef7a..c5026c6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,19 +2,33 @@ # yarn lockfile v1 +"@playwright/test@^1.51.1": + version "1.51.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.1.tgz#75357d513221a7be0baad75f01e966baf9c41a2e" + integrity sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q== + dependencies: + playwright "1.51.1" + +"@types/node@^22.13.9": + version "22.13.9" + resolved "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz" + integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw== + dependencies: + undici-types "~6.20.0" + argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -22,37 +36,42 @@ brace-expansion@^1.1.7: commander@~9.0.0: version "9.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.0.0.tgz#86d58f24ee98126568936bd1d3574e0308a99a40" + resolved "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz" integrity sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= deep-extend@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== entities@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + get-stdin@~9.0.0: version "9.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== glob@~7.2.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" @@ -64,12 +83,12 @@ glob@~7.2.0: ignore@~5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" @@ -77,36 +96,36 @@ inflight@^1.0.4: inherits@2: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsonc-parser@~3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== linkify-it@^3.0.1: version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz" integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== dependencies: uc.micro "^1.0.1" markdown-it@12.3.2: version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz" integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== dependencies: argparse "^2.0.1" @@ -117,7 +136,7 @@ markdown-it@12.3.2: markdownlint-cli@^0.31.1: version "0.31.1" - resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.31.1.tgz#8db34eec453e84bed06a954c8a289333f7c2c1c7" + resolved "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.31.1.tgz" integrity sha512-keIOMwQn+Ch7MoBwA+TdkyVMuxAeZFEGmIIlvwgV0Z1TGS5MxPnRr29XCLhkNzCHU+uNKGjU+VEjLX+Z9kli6g== dependencies: commander "~9.0.0" @@ -133,55 +152,69 @@ markdownlint-cli@^0.31.1: markdownlint-rule-helpers@~0.16.0: version "0.16.0" - resolved "https://registry.yarnpkg.com/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.16.0.tgz#c327f72782bd2b9475127a240508231f0413a25e" + resolved "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.16.0.tgz" integrity sha512-oEacRUVeTJ5D5hW1UYd2qExYI0oELdYK72k1TKGvIeYJIbqQWAz476NAc7LNixSySUhcNl++d02DvX0ccDk9/w== markdownlint@~0.25.1: version "0.25.1" - resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.25.1.tgz#df04536607ebeeda5ccd5e4f38138823ed623788" + resolved "https://registry.npmjs.org/markdownlint/-/markdownlint-0.25.1.tgz" integrity sha512-AG7UkLzNa1fxiOv5B+owPsPhtM4D6DoODhsJgiaNg1xowXovrYgOnLqAgOOFQpWOlHFVQUzjMY5ypNNTeov92g== dependencies: markdown-it "12.3.2" mdurl@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= minimatch@^3.0.4: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@~3.0.5: version "3.0.8" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz" integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== dependencies: brace-expansion "^1.1.7" minimist@^1.2.5: version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +playwright-core@1.51.1: + version "1.51.1" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz" + integrity sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw== + +playwright@1.51.1: + version "1.51.1" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz" + integrity sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw== + dependencies: + playwright-core "1.51.1" + optionalDependencies: + fsevents "2.3.2" + run-con@~1.2.10: version "1.2.10" - resolved "https://registry.yarnpkg.com/run-con/-/run-con-1.2.10.tgz#90de9d43d20274d00478f4c000495bd72f417d22" + resolved "https://registry.npmjs.org/run-con/-/run-con-1.2.10.tgz" integrity sha512-n7PZpYmMM26ZO21dd8y3Yw1TRtGABjRtgPSgFS/nhzfvbJMXFtJhJVyEgayMiP+w/23craJjsnfDvx4W4ue/HQ== dependencies: deep-extend "^0.6.0" @@ -191,15 +224,20 @@ run-con@~1.2.10: strip-json-comments@~3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=