diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..d033b05 --- /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@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm install + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index bf286cf..3ee7713 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ package-lock.json .vercel +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package.json b/package.json index 8528371..8256924 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,17 @@ "src" ], "scripts": { - "test": "echo \"Error: no test specified\" && exit 0", - "dev": "five-server --open=false" + "dev": "five-server --open=false", + "test": "playwright test", + "test:serve": "serve -l 3030" }, "keywords": [], "author": "Mikkel Malmberg (@mikker)", "license": "MIT", "devDependencies": { - "five-server": "^0.3.1" + "@playwright/test": "^1.40.1", + "five-server": "^0.3.1", + "serve": "^14.2.1" }, "repository": { "type": "git", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..77310f5 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,78 @@ +// @ts-check +const { defineConfig, devices } = require("@playwright/test"); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig({ + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* 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: process.env.CI ? "github" : "list", + /* 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://localhost:3030/tests", + + /* 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: { ...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 test:serve", + url: "http://localhost:3030/tests", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..397c535 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,32 @@ + + + + + Sourdough Test + + + + + + + + + + + + + diff --git a/tests/sourdough-toast.spec.js b/tests/sourdough-toast.spec.js new file mode 100644 index 0000000..813fdf9 --- /dev/null +++ b/tests/sourdough-toast.spec.js @@ -0,0 +1,51 @@ +// @ts-check +const { test, expect } = require("@playwright/test"); + +test.beforeEach(async ({ page }) => { + await page.goto("/tests"); +}); + +test("has title", async ({ page }) => { + await expect(page).toHaveTitle("Sourdough Test"); +}); + +test("a basic toast can render and then disappear after duration", async ({ + page, +}) => { + await page.getByRole("button", { name: "Basic" }).click(); + + await expect(page.locator("[data-sourdough-toast]")).toHaveCount(1); + await expect(page.locator("[data-sourdough-toast]")).toHaveCount(0); +}); + +test("only renders max toasts", async ({ page }) => { + await page.getByRole("button", { name: "Basic" }).click(); + await page.getByRole("button", { name: "Basic" }).click(); + await page.getByRole("button", { name: "Basic" }).click(); + await page.getByRole("button", { name: "Basic" }).click(); + + await expect(page.locator("[data-sourdough-toast]")).toHaveCount(3); +}); + +test("toast have types", async ({ page }) => { + await page.getByRole("button", { name: "Success" }).click(); + await expect(page.getByText("Success toast", { exact: true })).toHaveCount(1); + + await page.getByRole("button", { name: "Error" }).click(); + await expect(page.getByText("Error toast", { exact: true })).toHaveCount(1); + + await page.getByRole("button", { name: "Warning" }).click(); + await expect(page.getByText("Warning toast", { exact: true })).toHaveCount(1); + + await page.getByRole("button", { name: "Info" }).click(); + await expect(page.getByText("Info toast", { exact: true })).toHaveCount(1); +}); + +test("toasts don't dismiss when hovered", async ({ page }) => { + await page.getByRole("button", { name: "Basic" }).click(); + + await page.hover("[data-sourdough-toast]"); + await new Promise((resolve) => setTimeout(resolve, 5000)); + + await expect(page.locator("[data-sourdough-toast]")).toHaveCount(1); +});