diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..041160c --- /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: 16 + - 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@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 7329a85..3488e26 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ pnpm-debug.log* # macOS-specific files .DS_Store +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/README.md b/README.md index bd1bcbe..f649b71 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # LinkTree Free Clone +[![Playwright Tests](https://github.com/yoanbernabeu/LinkTreeFreeClone/actions/workflows/playwright.yml/badge.svg)](https://github.com/yoanbernabeu/LinkTreeFreeClone/actions/workflows/playwright.yml) [![Netlify Status](https://api.netlify.com/api/v1/badges/3e928715-44c6-4439-864e-6a346f699c07/deploy-status)](https://app.netlify.com/sites/linktreefreeclone/deploys) + ![screenshoot](readme.png) LinkTree Free Clone is a free clone of LinkTree, a popular link aggregator for social media. diff --git a/package-lock.json b/package-lock.json index 108fcb8..dea9d09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "@astrojs/tailwind": "^2.1.3", "astro": "^1.6.12", "tailwindcss": "^3.2.4" + }, + "devDependencies": { + "@playwright/test": "^1.28.1" } }, "node_modules/@ampproject/remapping": { @@ -646,6 +649,22 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", + "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.28.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -776,6 +795,12 @@ "@types/unist": "*" } }, + "node_modules/@types/node": { + "version": "18.11.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.12.tgz", + "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==", + "devOptional": true + }, "node_modules/@types/parse5": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", @@ -4363,6 +4388,18 @@ "node": ">=8" } }, + "node_modules/playwright-core": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", + "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", @@ -7211,6 +7248,16 @@ "tslib": "^2.4.0" } }, + "@playwright/test": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", + "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", + "dev": true, + "requires": { + "@types/node": "*", + "playwright-core": "1.28.1" + } + }, "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -7338,6 +7385,12 @@ "@types/unist": "*" } }, + "@types/node": { + "version": "18.11.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.12.tgz", + "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==", + "devOptional": true + }, "@types/parse5": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", @@ -9615,6 +9668,12 @@ } } }, + "playwright-core": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", + "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", + "dev": true + }, "postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", diff --git a/package.json b/package.json index 5900eea..6a63e7b 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,15 @@ "start": "astro dev", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "test": "playwright test" }, "dependencies": { "@astrojs/tailwind": "^2.1.3", "astro": "^1.6.12", "tailwindcss": "^3.2.4" + }, + "devDependencies": { + "@playwright/test": "^1.28.1" } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..4fe35aa --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,107 @@ +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 + }, + /* 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: { + /* 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 start', + port: 3000, + }, +}; + +export default config; diff --git a/tests/index.spec.ts b/tests/index.spec.ts new file mode 100644 index 0000000..fca67bb --- /dev/null +++ b/tests/index.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from '@playwright/test'; + +test('homepage has title and links', async ({ page }) => { + await page.goto('http://localhost:3000'); + + await expect(page).toHaveTitle(/LinkTreeFreeClone/); + + const twitter = page.getByRole('link', { name: '🐦 Twitter' }); + await expect(twitter).toHaveAttribute('href', 'https://twitter.com/yOyO38'); + + const youtube = page.getByRole('link', { name: '📺 Youtube' }); + await expect(youtube).toHaveAttribute('href', 'https://www.youtube.com/c/yoandevco'); + + const github = page.getByRole('link', { name: '🐙 Github' }); + await expect(github).toHaveAttribute('href', 'https://github.com/yoanbernabeu/LinkTreeFreeClone'); + + const blog = page.getByRole('link', { name: '📝 Blog' }); + await expect(blog).toHaveAttribute('href', 'https://yoandev.co'); + +});