diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 000000000..912bbf5a9 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,32 @@ +name: Playwright Tests +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + types: + - ready_for_review +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + env: + TESTER_USER_NAME: ${{ secrets.TESTER_USER_NAME }} + TESTER_PASSWORD: ${{ secrets.TESTER_PASSWORD }} + run: USER_NAME=TESTER_USER_NAME PASSWORD=TESTER_PASSWORD npx playwright test + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 1814e82a2..4bef3a9e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ dist/ yarn.lock yarn-error.log .env.local +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + +playwright/.auth diff --git a/package.json b/package.json index 75a3eec61..dbd34d5ca 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,11 @@ "preview": "vite preview", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", "lint:fix": "eslint --fix 'src/**/*.{jsx,ts,tsx}'", - "format": "prettier --write src/**/*.{js,jsx} --config ./.prettierrc" + "format": "prettier --write src/**/*.{js,jsx} --config ./.prettierrc", + "test": "npx playwright test", + "test-ui": "npx playwright test --ui", + "test-auth": "npx playwright test auth.setup.js", + "test-report": "yarn playwright show-report" }, "dependencies": { "@dnd-kit/core": "^6.0.8", @@ -52,10 +56,13 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@playwright/test": "^1.44.0", + "@types/node": "^20.12.12", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@vitejs/plugin-react": "^1.3.0", "babel-plugin-styled-components": "^2.0.7", + "dotenv": "^16.4.5", "eslint": "^8.30.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 000000000..41c702f6d --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,92 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test') + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +require('dotenv').config({ path: ['.env.local'] }) + +/** + * @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: '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://127.0.0.1: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: [ + // Setup project + { name: 'setup', testMatch: /.*\.setup\.js/ }, + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], // Use prepared auth state. + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], // Use prepared auth state. + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], // Use prepared auth state. + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + + /* 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: 'yarn dev', + url: 'http://127.0.0.1:3000', + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/tests/auth.setup.js b/tests/auth.setup.js new file mode 100644 index 000000000..e56d93254 --- /dev/null +++ b/tests/auth.setup.js @@ -0,0 +1,14 @@ +import { test as setup } from '@playwright/test' + +const authFile = 'playwright/.auth/user.json' + +setup('authenticate', async ({ request }) => { + // Send authentication request. Replace with your own. + await request.post('/api/auth/login', { + form: { + name: process.env.USER_NAME, + password: process.env.PASSWORD, + }, + }) + await request.storageState({ path: authFile }) +}) diff --git a/tests/example.spec.js b/tests/example.spec.js new file mode 100644 index 000000000..a19a05d2e --- /dev/null +++ b/tests/example.spec.js @@ -0,0 +1,10 @@ +// @ts-check +const { test, expect } = require('@playwright/test') + +// Check that the title of the page is correct (contains "Ayon"). +test('has title', async ({ page }) => { + await page.goto('/') + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Ayon/) +})