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");
+ });
+});