diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 4b7a8f7..cdf19ad 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -9,7 +9,34 @@ env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} jobs: - test: + test-frontend: + runs-on: ubuntu-latest + container: rust:latest + steps: + - name: Setup Environment + run: | + apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y + - uses: actions/checkout@v3 + - name: Run Test DB Script + run: ./test_db.sh + - name: Build + run: | + cargo build + cd frontend && npm install && npm run build + - name: Install dependencies + run: cd frontend && npm install + - name: Install Playwright Browsers + run: cd frontend && npx playwright install --with-deps + - name: Run Playwright tests + run: cd frontend && npx playwright test --workers 1 + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: frontend/playwright-report/ + retention-days: 30 + + test-backend: runs-on: ubuntu-latest container: rust:latest @@ -35,7 +62,7 @@ jobs: deploy-staging: runs-on: ubuntu-latest container: rust:latest - needs: [test] + needs: [test_frontend, test_backend] if: github.ref == 'refs/heads/staging' steps: - name: Setup Environment @@ -95,7 +122,7 @@ jobs: deploy-main: runs-on: ubuntu-latest container: rust:latest - needs: [test] + needs: [test_frontend, test_backend] if: github.ref == 'refs/heads/main' steps: - name: Setup Environment diff --git a/README.md b/README.md index 0a121d7..d6afcc6 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,22 @@ -# Frontend Process -´cd frontend´ -´npm install´ -´npm run (watch/build)´ +# Build -# Notes / Bugfixes ## Frontend -- [] support esc to close sidebar -- [] reload page -> don't throw input away! +1. `cd frontend` +2. `npm install` +3. `npm run (watch/build)` + +# Run ## Backend +1. `cargo r` + +# Test -# Nice to have ## Frontend -- [] my trips for cox +- `npx playwright test --workers 1 --project firefox` +- Nice UI: `--ui` +- Generate tests: `npx playwright codegen` + +## Backend (Unit + Integration) +`cargo t` + diff --git a/frontend/.gitignore b/frontend/.gitignore index d8b83df..bdf87f1 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1 +1,6 @@ package-lock.json +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/frontend/package.json b/frontend/package.json index 8b82d1e..0b42352 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,9 @@ "preview": "vite preview" }, "devDependencies": { + "@playwright/test": "^1.40.1", "@types/d3": "^7.4.1", + "@types/node": "^20.11.4", "autoprefixer": "^10.4.14", "postcss": "^8.4.21", "sass": "^1.60.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..a12a9b0 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,75 @@ +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: './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: [ + { + 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: 'cd .. && ./test_db.sh && cargo r', + }, +}); diff --git a/frontend/tests/cox.spec.ts b/frontend/tests/cox.spec.ts new file mode 100644 index 0000000..11f14d2 --- /dev/null +++ b/frontend/tests/cox.spec.ts @@ -0,0 +1,120 @@ +import { test, expect, Page } from '@playwright/test'; + +test('cox can create and delete trip', async ({ page }) => { + await page.goto('http://localhost:8000/auth'); + await page.getByPlaceholder('Name').click(); + await page.getByPlaceholder('Name').fill('cox'); + await page.getByPlaceholder('Name').press('Tab'); + await page.getByPlaceholder('Passwort').fill('cox'); + await page.getByPlaceholder('Passwort').press('Enter'); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await page.locator('.relative').first().click(); + await page.locator('#sidebar #planned_starting_time').click(); + await page.locator('#sidebar #planned_starting_time').fill('18:00'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.getByRole('spinbutton').fill('5'); + await page.getByRole('button', { name: 'Erstellen', exact: true }).click(); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await expect(page.locator('body')).toContainText('18:00 Uhr (cox) Details'); + + await page.goto('http://localhost:8000/planned'); + await page.getByRole('link', { name: 'Details' }).click(); + await page.getByRole('link', { name: 'Termin löschen' }).click(); + await expect(page.locator('body')).toContainText('Erfolgreich gelöscht!'); +}); + +// TODO: group -> cox can create trips +// TODO: cox can help/register at trips/events + +test.describe('cox can edit trips', () => { + let sharedPage: Page; + + test.beforeEach(async ({ browser }) => { + const page = await browser.newPage(); + + await page.goto('http://localhost:8000/auth'); + await page.getByPlaceholder('Name').click(); + await page.getByPlaceholder('Name').fill('cox'); + await page.getByPlaceholder('Name').press('Tab'); + await page.getByPlaceholder('Passwort').fill('cox'); + await page.getByPlaceholder('Passwort').press('Enter'); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await page.locator('.relative').first().click(); + await page.locator('#sidebar #planned_starting_time').click(); + await page.locator('#sidebar #planned_starting_time').fill('18:00'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.getByRole('spinbutton').fill('5'); + await page.getByRole('button', { name: 'Erstellen', exact: true }).click(); + + sharedPage = page; + }); + + test('edit remarks', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await sharedPage.locator('#sidebar #notes').click(); + await sharedPage.locator('#sidebar #notes').fill('Meine Anmerkung'); + await sharedPage.getByRole('button', { name: 'Speichern' }).click(); + await sharedPage.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Meine Anmerkung'); + + await sharedPage.getByRole('button', { name: 'Ausfahrt erstellen schließen' }).click(); + }); + + test('add and remove guest', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await sharedPage.locator('#sidebar #user_note').click(); + await sharedPage.locator('#sidebar #user_note').fill('Mein Gast'); + await sharedPage.getByRole('button', { name: 'Gast hinzufügen' }).click(); + await expect(sharedPage.locator('body')).toContainText('Erfolgreich angemeldet!'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 4'); + await expect(sharedPage.locator('#sidebar')).toContainText('Mein Gast (Gast) Abmelden'); + await expect(sharedPage.getByRole('link', { name: 'Termin löschen' })).not.toBeVisible(); + + await sharedPage.getByRole('link', { name: 'Abmelden' }).click(); + await expect(sharedPage.locator('body')).toContainText('Erfolgreich abgemeldet!'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 5'); + await expect(sharedPage.locator('#sidebar')).toContainText('Keine Ruderer angemeldet'); + await expect(sharedPage.getByRole('link', { name: 'Termin löschen' })).toBeVisible(); + + await sharedPage.getByRole('button', { name: 'Ausfahrt erstellen schließen' }).click(); + }); + + test('change amount rower', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 5'); + await sharedPage.getByRole('spinbutton').click(); + await sharedPage.getByRole('spinbutton').fill('3'); + await sharedPage.getByRole('button', { name: 'Speichern' }).click(); + await expect(sharedPage.locator('body')).toContainText('Ausfahrt erfolgreich aktualisiert.'); + await sharedPage.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + }); + + test('call off trip', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 5'); + await sharedPage.getByRole('spinbutton').click(); + await sharedPage.getByRole('spinbutton').fill('0'); + await sharedPage.getByRole('button', { name: 'Speichern' }).click(); + await expect(sharedPage.locator('body')).toContainText('Ausfahrt erfolgreich aktualisiert.'); + await sharedPage.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await expect(sharedPage.locator('body')).toContainText('(Absage cox )'); + }); + + test.afterEach(async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await sharedPage.getByRole('link', { name: 'Termin löschen' }).click(); + await sharedPage.close(); + }); + + // TODO: 'Immer anzeigen' (also verify the functionality), 'Gesperrt' + type +}); diff --git a/src/rest/mod.rs b/src/rest/mod.rs index b90bac0..ef69ad9 100644 --- a/src/rest/mod.rs +++ b/src/rest/mod.rs @@ -27,7 +27,7 @@ async fn login(login: Form>, db: &State) -> String { pub fn config(rocket: Rocket) -> Rocket { rocket - .mount("/", FileServer::from("svelte/build").rank(0)) + //.mount("/", FileServer::from("svelte/build").rank(0)) .mount("/api/login", routes![login]) } diff --git a/svelte/.gitignore b/svelte/.gitignore index 8f6c617..9280939 100644 --- a/svelte/.gitignore +++ b/svelte/.gitignore @@ -1,6 +1,5 @@ .DS_Store node_modules -/build /.svelte-kit /package .env diff --git a/templates/includes/macros.html.tera b/templates/includes/macros.html.tera index c2691b4..c1adb2d 100644 --- a/templates/includes/macros.html.tera +++ b/templates/includes/macros.html.tera @@ -209,7 +209,7 @@ {% if rower.is_real_guest %} (Gast) {% if allow_removing %} - Abmelden + Abmelden {% endif %} {% endif %}