diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..8a937ed --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,103 @@ +name: Playwright Tests +on: + pull_request: + push: + branches: + - master + - stable* + +env: + APP_NAME: files_photospheres + NC_REF: stable27 + NC_BIND: 'localhost:8080' + NODE_VER: 16 + DB_USER: root + DB_PASSWORD: rootpassword + DB_PORT: 4444 + # These env vars are used in vars.sh for setting up the tests + E2E_USER: admin + E2E_PASSWORD: admin + E2E_BASE_URL: 'http://localhost:8080' + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + services: + mysql: + image: mariadb:10.5 + ports: + - 4444:3306/tcp + env: + MYSQL_ROOT_PASSWORD: ${{ env.DB_PASSWORD }} + options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5 + steps: + - name: Checkout Nextcloud server + uses: actions/checkout@v3 + with: + repository: nextcloud/server + ref: ${{ env.NC_REF }} + + - name: Checkout server submodules + shell: bash + run: | + auth_header="$(git config --local --get http.https://github.com/.extraheader)" + git submodule sync --recursive + git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + + - name: Checkout app + uses: actions/checkout@v3 + with: + path: apps/${{ env.APP_NAME }} + + - name: Set up and run Nextcloud server + run: | + mkdir data + ./occ maintenance:install \ + --verbose \ + --database=mysql \ + --database-name=nextcloud \ + --database-host=127.0.0.1 \ + --database-port=${{ env.DB_PORT }} \ + --database-user=${{ env.DB_USER }} \ + --database-pass=${{ env.DB_PASSWORD }} \ + --admin-user ${{ env.E2E_USER }} \ + --admin-pass ${{ env.E2E_PASSWORD }} + ./occ app:enable ${{ env.APP_NAME }} + php -S ${{ env.NC_BIND }} & + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VER }} + + - name: Install dependencies + run: npm ci + working-directory: apps/${{ env.APP_NAME }}/tests/E2E + + - name: Install Playwright Browsers + run: npx playwright install chromium --with-deps + working-directory: apps/${{ env.APP_NAME }}/tests/E2E + + - name: Test setup + run: ./test-setup.sh + working-directory: apps/${{ env.APP_NAME }}/tests/E2E/scripts + + - name: Run Playwright tests + run: npx playwright test + working-directory: apps/${{ env.APP_NAME }}/tests/E2E + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: apps/${{ env.APP_NAME }}/tests/E2E/playwright-report/ + retention-days: 30 + + - name: Upload server logs + uses: actions/upload-artifact@v3 + if: failure() + with: + name: nextcloud-logs + path: data/nextcloud.log diff --git a/tests/E2E/.gitignore b/tests/E2E/.gitignore new file mode 100644 index 0000000..75e854d --- /dev/null +++ b/tests/E2E/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/tests/E2E/README.md b/tests/E2E/README.md new file mode 100644 index 0000000..0818e35 --- /dev/null +++ b/tests/E2E/README.md @@ -0,0 +1,18 @@ +# End to End Tests + +E2E testsuite implemented with [Playwright](https://playwright.dev/). + +- [End to End Tests](#end-to-end-tests) + - [Running the E2E tests](#running-the-e2e-tests) + +## Running the E2E tests + +To be able to run E2E tests, you will have to have a running Nextcloud instance with at least one user where you know the credentials. Also the `files_photospheres` app needs to be installed and enabled. + +1. Change into the directory `/tests/E2E` +2. Run `npm ci && npx playwright install chromium --with-deps` to install dependencies +3. Run `E2E_USER="" E2E_PASSWORD="" E2E_BASE_URL="" ./scripts/test-setup.sh` to upload the testdata +4. Execute the tests with `npx playwright test` +5. Use `E2E_USER="" E2E_PASSWORD="" E2E_BASE_URL="" ./scripts/test-shutdown.sh` to cleanup the testdata + +> Also have a look at [playwright.yml](./../../.github/workflows/playwright.yml) \ No newline at end of file diff --git a/tests/E2E/package-lock.json b/tests/E2E/package-lock.json new file mode 100644 index 0000000..d0e5951 --- /dev/null +++ b/tests/E2E/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "e2e", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "e2e", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.35.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.0.tgz", + "integrity": "sha512-6qXdd5edCBynOwsz1YcNfgX8tNWeuS9fxy5o59D0rvHXxRtjXRebB4gE4vFVfEMXl/z8zTnAzfOs7aQDEs8G4Q==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.35.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", + "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", + "dev": true + }, + "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/playwright-core": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.0.tgz", + "integrity": "sha512-muMXyPmIx/2DPrCHOD1H1ePT01o7OdKxKj2ebmCAYvqhUy+Y1bpal7B0rdoxros7YrXI294JT/DWw2LqyiqTPA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/tests/E2E/package.json b/tests/E2E/package.json new file mode 100644 index 0000000..4a2fe36 --- /dev/null +++ b/tests/E2E/package.json @@ -0,0 +1,13 @@ +{ + "name": "e2e", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.35.0" + } +} diff --git a/tests/E2E/playwright.config.ts b/tests/E2E/playwright.config.ts new file mode 100644 index 0000000..9ffac2e --- /dev/null +++ b/tests/E2E/playwright.config.ts @@ -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: './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://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, + // }, +}); diff --git a/tests/E2E/scripts/test-setup.sh b/tests/E2E/scripts/test-setup.sh new file mode 100755 index 0000000..be3e526 --- /dev/null +++ b/tests/E2E/scripts/test-setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +. "$SCRIPT_DIR/vars.sh" + +curl --fail-with-body -u $E2E_USER:$E2E_PASSWORD -X MKCOL "${E2E_BASE_URL}/ppv-testfiles" +curl --fail-with-body -u $E2E_USER:$E2E_PASSWORD -T "${SCRIPT_DIR}/../testdata/pano.jpg" "${E2E_BASE_URL}/ppv-testfiles/pano.jpg" +curl --fail-with-body -u $E2E_USER:$E2E_PASSWORD -T "${SCRIPT_DIR}/../testdata/non-pano.jpg" "${E2E_BASE_URL}/ppv-testfiles/non-pano.jpg" \ No newline at end of file diff --git a/tests/E2E/scripts/test-shutdown.sh b/tests/E2E/scripts/test-shutdown.sh new file mode 100755 index 0000000..03a353b --- /dev/null +++ b/tests/E2E/scripts/test-shutdown.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# This script is only needed locally to cleanup the test data + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +. "$SCRIPT_DIR/vars.sh" + +curl --fail-with-body -u $E2E_USER:$E2E_PASSWORD -X DELETE "${E2E_BASE_URL}/ppv-testfiles" diff --git a/tests/E2E/scripts/vars.sh b/tests/E2E/scripts/vars.sh new file mode 100644 index 0000000..5e38273 --- /dev/null +++ b/tests/E2E/scripts/vars.sh @@ -0,0 +1,4 @@ +#!/bin/bash +export E2E_USER=${E2E_USER:-admin} +export E2E_PASSWORD=${E2E_PASSWORD:-admin} +export E2E_BASE_URL="${E2E_BASE_URL:-"http://localhost/nextcloud"}/remote.php/dav/files/$E2E_USER" \ No newline at end of file diff --git a/tests/E2E/testdata/non-pano.jpg b/tests/E2E/testdata/non-pano.jpg new file mode 100644 index 0000000..b7ad6dc Binary files /dev/null and b/tests/E2E/testdata/non-pano.jpg differ diff --git a/tests/E2E/testdata/pano.jpg b/tests/E2E/testdata/pano.jpg new file mode 100644 index 0000000..47fd2e8 Binary files /dev/null and b/tests/E2E/testdata/pano.jpg differ diff --git a/tests/E2E/tests/show-noshow.spec.ts b/tests/E2E/tests/show-noshow.spec.ts new file mode 100644 index 0000000..fb27f67 --- /dev/null +++ b/tests/E2E/tests/show-noshow.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from '@playwright/test'; + +const frameId = '#photo-sphere-viewer-frame'; + +test.beforeEach(async ({ page }) => { + // Do the login and navigate to the folder where the test files have been uploaded + await page.goto(process.env.E2E_BASE_URL ? process.env.E2E_BASE_URL + '/index.php/login' : 'http://localhost/nextcloud/index.php/login'); + await page.locator('#user').click(); + await page.locator('#user').fill(process.env.E2E_USER ?? 'admin'); + await page.locator('#user').press('Tab'); + await page.locator('#password').fill(process.env.E2E_PASSWORD ?? 'admin'); + await page.locator('#password').press('Enter'); + await page.getByRole('link', { name: 'Files' }).click(); + await page.getByRole('link', { name: 'Not favorited ppv-testfiles Share Actions' }).click(); +}); + +test('PPV should show', async ({ page }) => { + await page.getByRole('link', { name: 'Not favorited pano .jpg Share Actions' }).click(); + + // Assert PPV opend + await page.locator(frameId).waitFor({ state: 'visible' }); + const ppvLocator = page.frameLocator(frameId); + + // Check autorotate and close buttons + await ppvLocator.getByTitle('Automatic rotation').getByRole('img').click(); + await ppvLocator.getByRole('button', { name: 'Close' }).click(); +}); + +test('PPV should not show', async ({ page }) => { + await page.getByRole('link', { name: 'Not favorited non-pano .jpg Share Actions' }).click(); + + // Assert PPV did not open + let visible = true; + try { + await page.locator(frameId).waitFor({ state: 'visible', timeout: 1000 }); + } catch (e) { + if (e.name !== 'TimeoutError') + throw e; + visible = false; + } + + expect(visible).toBe(false); +}); \ No newline at end of file