mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
playwright (#3)
* add playwright setup and tests for auth, profile and organization settings * add playwright github action * add sqlite database * fix playwright base url * add mailpit and parallelization * remove additional waitForUrl in fixture * fix tests * remove waitforurl in tests * change playwright github action to only one worker * try promiso all to avoid loading errors * change environment to include http protocol * convert back to simpler structure * add caching of playwright browser binaries * test if playwright in ci works faster with multiple workers * change back to one worker * remove browser binary caching * try using playwright container to speedup browser setup * rollback image changes * add playwright gitignore changes --------- Co-authored-by: Gregor Vostrak <gregorvostrak@Gregors-MacBook-Pro.local>
This commit is contained in:
54
.env.ci
Normal file
54
.env.ci
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
|
||||||
|
BROADCAST_DRIVER=log
|
||||||
|
CACHE_DRIVER=file
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=sync
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=mailpit
|
||||||
|
MAIL_PORT=1025
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_DEFAULT_REGION=us-east-1
|
||||||
|
AWS_BUCKET=
|
||||||
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
|
PUSHER_APP_ID=
|
||||||
|
PUSHER_APP_KEY=
|
||||||
|
PUSHER_APP_SECRET=
|
||||||
|
PUSHER_HOST=
|
||||||
|
PUSHER_PORT=443
|
||||||
|
PUSHER_SCHEME=https
|
||||||
|
PUSHER_APP_CLUSTER=mt1
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||||
|
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||||
|
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||||
|
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||||
|
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||||
63
.github/workflows/playwright.yml
vendored
Normal file
63
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: Playwright Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
mailpit:
|
||||||
|
image: 'axllent/mailpit:latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Run composer install
|
||||||
|
run: composer install -n --prefer-dist
|
||||||
|
|
||||||
|
- name: Create SQLite database
|
||||||
|
run: touch database/database.sqlite
|
||||||
|
|
||||||
|
- name: Prepare Laravel Application
|
||||||
|
run: |
|
||||||
|
cp .env.ci .env
|
||||||
|
php artisan key:generate
|
||||||
|
php artisan migrate --seed
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build Frontend
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Run Laravel Server
|
||||||
|
run: php artisan serve > /dev/null 2>&1 &
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install --with-deps
|
||||||
|
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: npx playwright test
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_BASE_URL: 'http://127.0.0.1:8000'
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: playwright-report/
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,3 +20,7 @@ yarn-error.log
|
|||||||
/.fleet
|
/.fleet
|
||||||
/.idea
|
/.idea
|
||||||
/.vscode
|
/.vscode
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -34,9 +34,24 @@ Additional System Requirements:
|
|||||||
Add the following entry to your `/etc/hosts`
|
Add the following entry to your `/etc/hosts`
|
||||||
|
|
||||||
```
|
```
|
||||||
127.0.0.1 time-tracking.local
|
127.0.0.1 timetracker.test
|
||||||
|
127.0.0.1 playwright.timetracker.test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Running E2E Tests
|
||||||
|
|
||||||
|
`./vendor/bin/sail up -d ` will automatically start a Playwright UI server that you can access at `https://playwright.timetracker.test`.
|
||||||
|
Make sure that you use HTTPS otherwise the resources will not be loaded correctly.
|
||||||
|
|
||||||
|
## Recording E2E Tests
|
||||||
|
|
||||||
|
To record E2E tests, you need to install and execute playwright locally using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright install
|
||||||
|
npx playwright codegen timetracker.test
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This project is in a very early stage. The structure and APIs are still subject to change and not stable.
|
This project is in a very early stage. The structure and APIs are still subject to change and not stable.
|
||||||
|
|||||||
@@ -57,6 +57,31 @@ services:
|
|||||||
- '${DB_USERNAME}'
|
- '${DB_USERNAME}'
|
||||||
retries: 3
|
retries: 3
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
mailpit:
|
||||||
|
image: 'axllent/mailpit:latest'
|
||||||
|
ports:
|
||||||
|
- '${FORWARD_MAILPIT_PORT:-1025}:1025'
|
||||||
|
- '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025'
|
||||||
|
networks:
|
||||||
|
- sail
|
||||||
|
playwright:
|
||||||
|
image: mcr.microsoft.com/playwright:v1.41.1-jammy
|
||||||
|
command: ['npx', 'playwright', 'test', '--ui-port=8080', '--ui-host=0.0.0.0']
|
||||||
|
working_dir: /src
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=${NETWORK_NAME}"
|
||||||
|
- "traefik.http.routers.playwright.rule=Host(`playwright.${NGINX_HOST_NAME}`)"
|
||||||
|
- "traefik.http.routers.playwright.entrypoints=web"
|
||||||
|
- "traefik.http.services.playwright.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.playwright-https.rule=Host(`playwright.${NGINX_HOST_NAME}`)"
|
||||||
|
- "traefik.http.routers.playwright-https.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.playwright-https.tls=true"
|
||||||
|
networks:
|
||||||
|
- sail
|
||||||
|
- reverse-proxy
|
||||||
|
volumes:
|
||||||
|
- '.:/src'
|
||||||
networks:
|
networks:
|
||||||
reverse-proxy:
|
reverse-proxy:
|
||||||
name: "${NETWORK_NAME}"
|
name: "${NETWORK_NAME}"
|
||||||
|
|||||||
54
e2e/auth.spec.ts
Normal file
54
e2e/auth.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { PLAYWRIGHT_BASE_URL } from '../playwright/config';
|
||||||
|
|
||||||
|
async function registerNewUser(page, email, password) {
|
||||||
|
await page.getByRole('link', { name: 'Register' }).click();
|
||||||
|
await page.getByLabel('Name').fill('John Doe');
|
||||||
|
await page.getByLabel('Email').fill(email);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(password);
|
||||||
|
await page.getByLabel('Confirm Password').fill(password);
|
||||||
|
await page.getByRole('button', { name: 'Register' }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Dashboard' })
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('can register, logout and log back in', async ({ page }) => {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL);
|
||||||
|
const email = `john+${Math.round(Math.random() * 10000)}@doe.com`;
|
||||||
|
const password = 'suchagreatpassword123';
|
||||||
|
await registerNewUser(page, email, password);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: "John's Organization" })
|
||||||
|
).toBeVisible();
|
||||||
|
await page.locator('#currentUserButton').click();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Log Out' }).click();
|
||||||
|
await page.waitForURL(PLAYWRIGHT_BASE_URL + '/');
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/login');
|
||||||
|
await page.getByLabel('Email').fill(email);
|
||||||
|
await page.getByLabel('Password').fill(password);
|
||||||
|
await page.getByRole('button', { name: 'Log in' }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Dashboard' })
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can register and delete account', async ({ page }) => {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL);
|
||||||
|
const email = `john+${Math.round(Math.random() * 10000)}@doe.com`;
|
||||||
|
const password = 'suchagreatpassword123';
|
||||||
|
await registerNewUser(page, email, password);
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/user/profile');
|
||||||
|
await page.getByRole('button', { name: 'Delete Account' }).click();
|
||||||
|
await page.getByPlaceholder('Password').fill(password);
|
||||||
|
await page.getByRole('button', { name: 'Delete Account' }).nth(1).click();
|
||||||
|
await page.waitForURL(PLAYWRIGHT_BASE_URL + '/');
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/login');
|
||||||
|
await page.getByLabel('Email').fill(email);
|
||||||
|
await page.getByLabel('Password').fill(password);
|
||||||
|
await page.getByRole('button', { name: 'Log in' }).click();
|
||||||
|
await expect(page.getByRole('paragraph')).toContainText(
|
||||||
|
'These credentials do not match our records.'
|
||||||
|
);
|
||||||
|
});
|
||||||
50
e2e/organization.spec.ts
Normal file
50
e2e/organization.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { test, expect } from '../playwright/fixtures';
|
||||||
|
import { PLAYWRIGHT_BASE_URL } from '../playwright/config';
|
||||||
|
|
||||||
|
async function goToOrganizationSettings(page) {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/dashboard');
|
||||||
|
await page.locator('#currentTeamButton').click();
|
||||||
|
await page.getByRole('link', { name: 'Team Settings' }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('test that organization name can be updated', async ({ page }) => {
|
||||||
|
await goToOrganizationSettings(page);
|
||||||
|
await page.getByLabel('Team Name').fill('NEW ORG NAME');
|
||||||
|
await page.getByLabel('Team Name').press('Enter');
|
||||||
|
await page.getByLabel('Team Name').press('Meta+r');
|
||||||
|
await expect(page.getByRole('navigation')).toContainText('NEW ORG NAME');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that new editor can be invited', async ({ page }) => {
|
||||||
|
await goToOrganizationSettings(page);
|
||||||
|
const editorId = Math.round(Math.random() * 10000);
|
||||||
|
await page.getByLabel('Email').fill(`new+${editorId}@editor.test`);
|
||||||
|
await page.getByRole('button', { name: 'Editor' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Add' }).click();
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByRole('main')).toContainText(
|
||||||
|
`new+${editorId}@editor.test`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that new admin can be invited', async ({ page }) => {
|
||||||
|
await goToOrganizationSettings(page);
|
||||||
|
const adminId = Math.round(Math.random() * 10000);
|
||||||
|
await page.getByLabel('Email').fill(`new+${adminId}@admin.test`);
|
||||||
|
await page.getByRole('button', { name: 'Administrator' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Add' }).click();
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByRole('main')).toContainText(
|
||||||
|
`new+${adminId}@admin.test`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('test that error shows if no role is selected', async ({ page }) => {
|
||||||
|
await goToOrganizationSettings(page);
|
||||||
|
const noRoleId = Math.round(Math.random() * 10000);
|
||||||
|
|
||||||
|
await page.getByLabel('Email').fill(`new+${noRoleId}@norole.test`);
|
||||||
|
await page.getByRole('button', { name: 'Add' }).click();
|
||||||
|
await expect(page.getByRole('main')).toContainText(
|
||||||
|
'The role field is required.'
|
||||||
|
);
|
||||||
|
});
|
||||||
21
e2e/profile.spec.ts
Normal file
21
e2e/profile.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { test, expect } from '../playwright/fixtures';
|
||||||
|
import { PLAYWRIGHT_BASE_URL } from '../playwright/config';
|
||||||
|
|
||||||
|
test('test that user name can be updated', async ({ page }) => {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/user/profile');
|
||||||
|
await page.getByLabel('Name').fill('NEW NAME');
|
||||||
|
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByLabel('Name')).toHaveValue('NEW NAME');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that user email can be updated', async ({ page }) => {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/user/profile');
|
||||||
|
const emailId = Math.round(Math.random() * 10000);
|
||||||
|
await page.getByLabel('Email').fill(`newemail+${emailId}@test.com`);
|
||||||
|
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByLabel('Email')).toHaveValue(
|
||||||
|
`newemail+${emailId}@test.com`
|
||||||
|
);
|
||||||
|
});
|
||||||
67
package-lock.json
generated
67
package-lock.json
generated
@@ -11,8 +11,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "^1.0.0",
|
"@inertiajs/vue3": "^1.0.0",
|
||||||
|
"@playwright/test": "^1.41.1",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.2",
|
||||||
|
"@types/node": "^20.11.5",
|
||||||
"@types/ziggy-js": "^1.8.0",
|
"@types/ziggy-js": "^1.8.0",
|
||||||
"@vitejs/plugin-vue": "^4.5.0",
|
"@vitejs/plugin-vue": "^4.5.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
@@ -883,6 +885,21 @@
|
|||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.41.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.1.tgz",
|
||||||
|
"integrity": "sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.41.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.9.5",
|
"version": "4.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz",
|
||||||
@@ -1100,8 +1117,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
|
||||||
"integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
|
"integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
@@ -3499,6 +3514,50 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.41.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.1.tgz",
|
||||||
|
"integrity": "sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.41.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.41.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.1.tgz",
|
||||||
|
"integrity": "sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.33",
|
"version": "8.4.33",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||||
@@ -4336,9 +4395,7 @@
|
|||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
|||||||
@@ -6,12 +6,15 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore .",
|
"lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore .",
|
||||||
"lint:fix": "eslint --fix --ext .js,.vue,.ts --ignore-path .gitignore .",
|
"lint:fix": "eslint --fix --ext .js,.vue,.ts --ignore-path .gitignore .",
|
||||||
"type-check": "vue-tsc --noEmit"
|
"type-check": "vue-tsc --noEmit",
|
||||||
|
"test:e2e": "npx playwright test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "^1.0.0",
|
"@inertiajs/vue3": "^1.0.0",
|
||||||
|
"@playwright/test": "^1.41.1",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.2",
|
||||||
|
"@types/node": "^20.11.5",
|
||||||
"@types/ziggy-js": "^1.8.0",
|
"@types/ziggy-js": "^1.8.0",
|
||||||
"@vitejs/plugin-vue": "^4.5.0",
|
"@vitejs/plugin-vue": "^4.5.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
|
|||||||
77
playwright.config.ts
Normal file
77
playwright.config.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './e2e',
|
||||||
|
/* 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: [
|
||||||
|
{
|
||||||
|
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 start',
|
||||||
|
// url: 'http://127.0.0.1:3000',
|
||||||
|
// reuseExistingServer: !process.env.CI,
|
||||||
|
// },
|
||||||
|
});
|
||||||
2
playwright/config.ts
Normal file
2
playwright/config.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const PLAYWRIGHT_BASE_URL =
|
||||||
|
process.env.PLAYWRIGHT_BASE_URL ?? 'http://laravel.test';
|
||||||
71
playwright/fixtures.ts
Normal file
71
playwright/fixtures.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { expect, test as baseTest } from '@playwright/test';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { PLAYWRIGHT_BASE_URL } from './config';
|
||||||
|
|
||||||
|
export * from '@playwright/test';
|
||||||
|
export const test = baseTest.extend<object, { workerStorageState: string }>({
|
||||||
|
// Use the same storage state for all tests in this worker.
|
||||||
|
storageState: ({ workerStorageState }, use) => use(workerStorageState),
|
||||||
|
|
||||||
|
// Authenticate once per worker with a worker-scoped fixture.
|
||||||
|
workerStorageState: [
|
||||||
|
async ({ browser }, use) => {
|
||||||
|
// Use parallelIndex as a unique identifier for each worker.
|
||||||
|
const id = test.info().parallelIndex;
|
||||||
|
const fileName = path.resolve(
|
||||||
|
test.info().project.outputDir,
|
||||||
|
`.auth/${id}.json`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fs.existsSync(fileName)) {
|
||||||
|
// Reuse existing authentication state if any.
|
||||||
|
await use(fileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Important: make sure we authenticate in a clean environment by unsetting storage state.
|
||||||
|
const page = await browser.newPage({ storageState: undefined });
|
||||||
|
|
||||||
|
// Acquire a unique account, for example create a new one.
|
||||||
|
// Alternatively, you can have a list of precreated accounts for testing.
|
||||||
|
// Make sure that accounts are unique, so that multiple team members
|
||||||
|
// can run tests at the same time without interference.
|
||||||
|
// const account = await acquireAccount(id);
|
||||||
|
|
||||||
|
// TODO: Use Seeder Accounts instead of creating new ones
|
||||||
|
|
||||||
|
// Perform authentication steps. Replace these actions with your own.
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/register');
|
||||||
|
await page.getByLabel('Name').fill('John Doe');
|
||||||
|
await page
|
||||||
|
.getByLabel('Email')
|
||||||
|
.fill(`john+${Math.round(Math.random() * 10000)}@doe.com`);
|
||||||
|
await page
|
||||||
|
.getByLabel('Password', { exact: true })
|
||||||
|
.fill('amazingpassword123');
|
||||||
|
await page
|
||||||
|
.getByLabel('Confirm Password')
|
||||||
|
.fill('amazingpassword123');
|
||||||
|
await page.getByRole('button', { name: 'Register' }).click();
|
||||||
|
|
||||||
|
// Wait until the page receives the cookies.
|
||||||
|
//
|
||||||
|
// Sometimes login flow sets cookies in the process of several redirects.
|
||||||
|
// Wait for the final URL to ensure that the cookies are actually set.
|
||||||
|
await page.waitForURL(PLAYWRIGHT_BASE_URL + '/dashboard');
|
||||||
|
|
||||||
|
// Alternatively, you can wait until the page reaches a state where all cookies are set.
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Dashboard' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// End of authentication steps.
|
||||||
|
|
||||||
|
await page.context().storageState({ path: fileName });
|
||||||
|
await page.close();
|
||||||
|
await use(fileName);
|
||||||
|
},
|
||||||
|
{ scope: 'worker' },
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -87,6 +87,7 @@ const logout = () => {
|
|||||||
<span class="inline-flex rounded-md">
|
<span class="inline-flex rounded-md">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
id="currentTeamButton"
|
||||||
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none focus:bg-gray-50 dark:focus:bg-gray-700 active:bg-gray-50 dark:active:bg-gray-700 transition ease-in-out duration-150">
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none focus:bg-gray-50 dark:focus:bg-gray-700 active:bg-gray-50 dark:active:bg-gray-700 transition ease-in-out duration-150">
|
||||||
{{
|
{{
|
||||||
page.props.auth.user
|
page.props.auth.user
|
||||||
@@ -209,6 +210,7 @@ const logout = () => {
|
|||||||
page.props.jetstream
|
page.props.jetstream
|
||||||
.managesProfilePhotos
|
.managesProfilePhotos
|
||||||
"
|
"
|
||||||
|
id="currentUserButton"
|
||||||
class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition">
|
class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition">
|
||||||
<img
|
<img
|
||||||
class="h-8 w-8 rounded-full object-cover"
|
class="h-8 w-8 rounded-full object-cover"
|
||||||
|
|||||||
Reference in New Issue
Block a user