diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..fd5663c --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '14.x' + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index e4504a3..acef850 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ node_modules npm-debug.log* yarn-debug.log* yarn-error.log* +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index 0323a4e..869816e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "@oakstudios/mechanical-ragger", "version": "0.5", "license": "MIT", - "dependencies": { - "storybook-addon-run-script": "^0.1.4" - }, "devDependencies": { "@babel/cli": "^7.12.13", "@babel/core": "^7.16.0", @@ -18,6 +15,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.12.13", "@babel/preset-typescript": "^7.16.7", + "@playwright/test": "^1.22.0", "@rollup/plugin-babel": "^5.2.3", "@rollup/plugin-node-resolve": "^13.0.6", "@storybook/addon-actions": "^6.4.22", @@ -43,6 +41,7 @@ "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.1", + "storybook-addon-run-script": "^0.1.4", "tslib": "^2.3.1", "typescript": "^4.5.3" }, @@ -54,6 +53,12 @@ "react-dom": "^17.0.2" } }, + "@playwright/test": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.22.0.tgz", + "integrity": "sha512-ExcAjiECo3uTG5Sl5H4a7rKp/5TEHTI87dv9NHYEoUFuOHPhSVxB7QsuM70ktO+wTTZ9KzhwzcegxAGRmUFKEA==", + "extraneous": true + }, "node_modules/@babel/cli": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.0.tgz", @@ -3174,6 +3179,21 @@ "node": ">=10" } }, + "node_modules/@playwright/test": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.22.0.tgz", + "integrity": "sha512-ExcAjiECo3uTG5Sl5H4a7rKp/5TEHTI87dv9NHYEoUFuOHPhSVxB7QsuM70ktO+wTTZ9KzhwzcegxAGRmUFKEA==", + "dev": true, + "dependencies": { + "playwright-core": "1.22.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.6.tgz", @@ -17588,6 +17608,18 @@ "node": ">=8" } }, + "node_modules/playwright-core": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.0.tgz", + "integrity": "sha512-XnDPiV4NCzTtXWxQdyJ6Wg8xhST3ciUjt5mITaxoqOoYggmRtofKm0PXLehfbetXh2ppPYj5U8UhtUpdIE4wag==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/pnp-webpack-plugin": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", @@ -20267,7 +20299,8 @@ "node_modules/storybook-addon-run-script": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/storybook-addon-run-script/-/storybook-addon-run-script-0.1.4.tgz", - "integrity": "sha512-jyYvnbpM18dcCL765rK5XZrk93OnC1cYdK/jOConpkw5Zin8JR5Bzzr43/WpunJn9XMwZvvaozPoOemGIsZJBw==" + "integrity": "sha512-jyYvnbpM18dcCL765rK5XZrk93OnC1cYdK/jOConpkw5Zin8JR5Bzzr43/WpunJn9XMwZvvaozPoOemGIsZJBw==", + "dev": true }, "node_modules/stream-browserify": { "version": "2.0.2", @@ -25351,6 +25384,15 @@ "rimraf": "^3.0.2" } }, + "@playwright/test": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.22.0.tgz", + "integrity": "sha512-ExcAjiECo3uTG5Sl5H4a7rKp/5TEHTI87dv9NHYEoUFuOHPhSVxB7QsuM70ktO+wTTZ9KzhwzcegxAGRmUFKEA==", + "dev": true, + "requires": { + "playwright-core": "1.22.0" + } + }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.6.tgz", @@ -36464,6 +36506,12 @@ "find-up": "^4.0.0" } }, + "playwright-core": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.0.tgz", + "integrity": "sha512-XnDPiV4NCzTtXWxQdyJ6Wg8xhST3ciUjt5mITaxoqOoYggmRtofKm0PXLehfbetXh2ppPYj5U8UhtUpdIE4wag==", + "dev": true + }, "pnp-webpack-plugin": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", @@ -38621,7 +38669,8 @@ "storybook-addon-run-script": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/storybook-addon-run-script/-/storybook-addon-run-script-0.1.4.tgz", - "integrity": "sha512-jyYvnbpM18dcCL765rK5XZrk93OnC1cYdK/jOConpkw5Zin8JR5Bzzr43/WpunJn9XMwZvvaozPoOemGIsZJBw==" + "integrity": "sha512-jyYvnbpM18dcCL765rK5XZrk93OnC1cYdK/jOConpkw5Zin8JR5Bzzr43/WpunJn9XMwZvvaozPoOemGIsZJBw==", + "dev": true }, "stream-browserify": { "version": "2.0.2", diff --git a/package.json b/package.json index 80a1553..ec28b12 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "@oakstudios/mechanical-ragger", "version": "0.5", "description": "A layout tool that automatically rags long text by line", - "type": "module", "main": "./index.js", "types": "./index.d.ts", "exports": { @@ -29,6 +28,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.12.13", "@babel/preset-typescript": "^7.16.7", + "@playwright/test": "^1.22.0", "@rollup/plugin-babel": "^5.2.3", "@rollup/plugin-node-resolve": "^13.0.6", "@storybook/addon-actions": "^6.4.22", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..415fe90 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,105 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* 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: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* 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: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + }, + }, + + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + }, + }, + + /* 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: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run storybook', + port: 6006, + }, +}; + +export default config; diff --git a/src/__tests__/components.spec.js b/src/__tests__/components.spec.js index 0f30e54..74845e4 100644 --- a/src/__tests__/components.spec.js +++ b/src/__tests__/components.spec.js @@ -35,29 +35,6 @@ test("the web component is created without errors.", async () => { expect(el).toHaveProperty("ragger"); }); -test("the web component reflects updated content.", async () => { - const container = document.createElement("div"); - container.innerHTML = "one"; - const el = container.querySelector("mechanical-ragger"); - el.innerHTML = "two"; - // el.shadowRoot seems like it's not thoroughly implemented in JSDOM. - expect(el.shadowRoot).toHaveTextContent("two"); -}); - -test("the web component's slot element inherit styles.", async () => { - const container = document.createElement("div"); - const style = (document.createElement( - "style" - ).innerHTML = `a { border: 1px solid blue; }`); - container.innerHTML = - "one"; - document.body.append(container, style); - const el = container.querySelector("mechanical-ragger a"); - // jsdom doessn't return anything from getComputedStyle beside visibility. - const borderStyle = window.getComputedStyle(el).border; - expect(borderStyle).toEqual("1px solid blue"); -}); - test("the web component calls unobserve when it's removed from the DOM.", async () => { const el = document.createElement("mechanical-ragger"); document.body.appendChild(el); diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 0000000..75bc796 --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,26 @@ +import { test, expect, Page } from "@playwright/test"; + +test.beforeEach(async ({ page }) => { + await page.goto( + "http://localhost:6006/iframe.html?id=web-component--slotted-element-styles" + ); +}); + +test.describe("Web Component", () => { + test("should allow slotted elements to be targetted by page CSS", async ({ + page, + }) => { + const element = await page.locator("mechanical-ragger a"); + await expect(element).toHaveCSS("border-bottom-color", "rgb(255, 0, 0)"); + }); + test("should propagate content updates to the shadow dom", async ({ + page, + }) => { + const ragger = await page.waitForSelector("mechanical-ragger p"); + await ragger.evaluate((el) => { + el.innerHTML = "two"; + }); + const element = await page.locator("mechanical-ragger >>> p"); + await expect(element).toHaveText("two"); + }); +});