Skip to content

Commit

Permalink
Rewrite tests for NC 28 using Playwright. Update Readme.
Browse files Browse the repository at this point in the history
  • Loading branch information
te-online committed Mar 23, 2024
1 parent 77b11b0 commit e465a6d
Show file tree
Hide file tree
Showing 7 changed files with 577 additions and 6 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -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@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
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ node_modules
.env
tests/cypress/screenshots
tests/results
cypress.env.json
cypress.env.json
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,59 @@ Unit tests are run using [Jest](https://jestjs.io/). Tests can be found in the `

### End-to-end tests

Some end-to-end testing is done using [Cypress](https://www.cypress.io/). Tests currently cover basic features of the app and run in a headless Chrome browser on a dockerized Nextcloud instance. For this repository tests run on [a mirror repository on Gitlab](https://gitlab.com/te-online/files_linkeditor).
#### Introduction and docker-compose setup

To run tests locally, change into the `tests` directory. Before running or working on tests, run `npm install` to install dependencies. Make sure to create a `.env` file based on `.env.example` and a `cypress.env.json` based on `cypress.env.example.json`; values can be chosen freely, only make sure they align between the two files.
Make sure to create a `.env` file based on `.env.example` in the `tests` folder.

To run the app install docker and docker-compose on your machine. Then run `docker-compose up`, followed by `docker exec $(docker ps -qf "name=app") sh -c 'chown www-data:root custom_apps'`. Wait for the app to be ready, then run `docker exec -u www-data $(docker ps -qf "name=app") sh -c 'php -f ./occ app:disable firstrunwizard'`.
To run the app, install docker and docker-compose on your machine. Then run

Finally, run `npm start` and use Cypress' UI to start running the tests included in the spec file.
```bash
$ docker-compose up
```

To shut down containers and delete the temporary volumes, run `docker-compose down -v` in a second terminal while you're in the same `tests` directory.
Let this run to be able to see the output. Add `-d` if you want docker-compose to run in the background.

In a new terminal run

```bash
# ATTENTION: You have to be quick with this command, before installation times out
$ docker exec $(docker ps -qf "name=app") sh -c 'chown www-data:root custom_apps'
# ATTENTION: Wait here for the app to be ready, then run:
$ docker exec -u www-data $(docker ps -qf "name=app") sh -c 'php -f ./occ app:disable firstrunwizard'
$ docker exec -u www-data $(docker ps -qf "name=app") sh -c 'php -f ./occ app:enable files_linkeditor'
```

To shut down containers and delete the temporary volumes, run

```bash
# This will DELETE your test data
$ docker-compose down -v
```

in a second terminal while you're in the same `tests` directory.

Test cases might depend on running in a specific order. This means you might need to wind down your docker containers and start them again as described above when re-running tests.

**New features** are supposed to be covered by end-to-end tests in a way that reflects their average usage.

**Bugfixes** are supposed to include a test case that demonstrates the bug being fixed and prevent it to be re-introduced in the future.

#### Nextcloud < 28: Cypress

End-to-end testing is done using [Cypress](https://www.cypress.io/). Tests currently cover basic features of the app and run in a headless Chrome browser on a dockerized Nextcloud instance. For this repository tests run on [a mirror repository on Gitlab](https://gitlab.com/te-online/files_linkeditor).

To run tests locally, change into the `tests` directory. Before running or working on tests, run `npm install` to install dependencies. Make sure you have created a `.env` file based on `.env.example` and a `cypress.env.json` based on `cypress.env.example.json` in the `tests` folder; values can be chosen freely, only make sure they align between the two files.

Run `npm start` and use Cypress' UI to start running the tests included in the spec file.

#### Nextcloud >= 28: Playwright

End-to-end testing (manually before release) is done using [Playwright](https://playwright.dev/). Tests currently cover basic features of the app and run in a headless Chrome browser on a dockerized Nextcloud instance.

To run tests locally, install the ["Playwright Test for VSCode extension"](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). Before running or working on tests, run `npm install` to install dependencies. Make sure you have create a `.env` file based on `.env.example` in the `tests` folder; values can be chosen freely.

Use the "Testing" section in VSCode to run the Playwright tests (or [consult the official documentation](https://playwright.dev/docs/running-tests) to find alternative run methods).

## Changelog

The changelog is available [in the CHANGELOG file](https://github.com/te-online/files_linkeditor/blob/main/CHANGELOG.md)
276 changes: 276 additions & 0 deletions e2e/linkeditor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import { test, expect, type Page } from "@playwright/test";

const adminLogin = async (page: Page) => {
await page.getByPlaceholder("Account name or email").fill(process.env.NEXTCLOUD_ADMIN_USER!);
await page.getByPlaceholder("Account name or email").press("Tab");
await page.getByPlaceholder("Password").fill(process.env.NEXTCLOUD_ADMIN_PASSWORD!);
await page.getByPlaceholder("Password").press("Enter");
};

const testUserLogin = async (page: Page) => {
// Log in as other user
await page.getByPlaceholder("Account name or email").fill("testuser-1");
await page.getByPlaceholder("Account name or email").press("Tab");
await page.getByPlaceholder("Password").fill("testuser-1-password");
await page.getByPlaceholder("Password").press("Enter");
};

const testFileViewer = async (
page: Page,
{ fileName, url, _blank, close }: { fileName: string; url: string; _blank: boolean; close?: boolean },
) => {
await page.locator("h3").filter({ hasText: fileName }).isVisible();

await page.getByText(`You are about to visit: ${url}`).isVisible();

if (_blank) {
const page1Promise = page.waitForEvent("popup");
await page.getByRole("link", { name: url }).click();
const page1 = await page1Promise;
expect(page1.url()).toBe(`${url}/`);
page1.close();
}

await page.getByRole("link", { name: "Visit link" }).isVisible();
expect(await page.getByRole("link", { name: "Visit link" }).getAttribute("href")).toBe(url);

if (_blank) {
expect(await page.getByRole("link", { name: "Visit link" }).getAttribute("target")).toBe("_blank");
}

if (close === false) {
return;
}

await page.getByRole("link", { name: "Cancel" }).click();
await expect(page.getByRole("heading", { name: "Test File.URL" })).not.toBeVisible();
};

test.beforeEach(async ({ page, context }) => {
// Grant clipboard permissions to browser context
await context.grantPermissions(["clipboard-read", "clipboard-write"]);

await page.goto("http://localhost:8000");
});

test.describe("Preparation", () => {
test("create test user", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Settings menu").click();
await page.getByRole("link", { name: "Users" }).click();
await page.getByRole("button", { name: "New user" }).click();
await page.locator('[data-test="username"]').fill("testuser-1");
await page.locator('[data-test="password"]').fill("testuser-1-password");
await page.locator('[data-test="submit"]').click();
await page.getByRole("cell", { name: "testuser-1 testuser-" }).getByRole("strong").isVisible();
});
});

test.describe("Link File Creation", () => {
test("create a .URL link file", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();
await page.getByRole("button", { name: "New" }).click();
await page.getByRole("menuitem", { name: "New link (.URL)" }).click();
await page.getByLabel("File name").fill("Test File.URL");
await page.getByRole("button", { name: "Create" }).click();
await page.getByPlaceholder("e.g. https://example.org").fill("https://example.org");
await page.getByRole("link", { name: "Save" }).click();
// Space is produced by two <span> around filename and suffix
await page.getByRole("link", { name: "Test File .URL" }).scrollIntoViewIfNeeded();
await expect(page.getByRole("link", { name: "Test File .URL" })).toBeVisible();
});

test("create a .webloc link file", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();
await page.getByRole("button", { name: "New" }).click();
await page.getByRole("menuitem", { name: "New link (.webloc)" }).click();
await page.getByLabel("File name").fill("Test File.webloc");
await page.getByRole("button", { name: "Create" }).click();
await page.getByPlaceholder("e.g. https://example.org").fill("https://example.org");
await page.getByRole("link", { name: "Save" }).click();
await page.getByRole("link", { name: "Test File .webloc" }).scrollIntoViewIfNeeded();
await expect(page.getByRole("link", { name: "Test File .webloc" })).toBeVisible();
});

test("open an existing .URL link file", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();
await page.getByRole("link", { name: "Test File .URL" }).scrollIntoViewIfNeeded();
await expect(page.getByRole("link", { name: "Test File .URL" })).toBeVisible();
await page.getByRole("link", { name: "Test File .URL" }).click();

await testFileViewer(page, { fileName: "Test File.URL", url: "https://example.org", _blank: true });
});

test("open an existing .webloc link file", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();
await page.getByRole("link", { name: "Test File .webloc" }).scrollIntoViewIfNeeded();
await expect(page.getByRole("link", { name: "Test File .webloc" })).toBeVisible();
await page.getByRole("link", { name: "Test File .webloc" }).click();

await testFileViewer(page, { fileName: "Test File.webloc", url: "https://example.org", _blank: true });
});

test("open link in same tab", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();
await page.getByRole("link", { name: "Test File .URL" }).scrollIntoViewIfNeeded();
await page
.getByRole("row", { name: 'Toggle selection for file "Test File.URL" Test File .URL Show sharing options' })
.getByLabel("Actions")
.click();
await page.getByRole("menuitem", { name: "Edit link" }).click();
await page.getByText("Open in same window", { exact: true }).click();
await page.getByRole("link", { name: "Save" }).click();
await page.getByRole("link", { name: "Test File .URL" }).click();

await testFileViewer(page, { fileName: "Test File.URL", url: "https://example.org", _blank: false, close: false });

await page.getByRole("link", { name: "Visit link" }).click();
expect(page.url()).toBe("https://example.org/");
});

test("open link in same tab without confirmation", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();

await page.getByRole("link", { name: "Test File .URL" }).scrollIntoViewIfNeeded();
await page.getByRole("row", { name: 'Toggle selection for file "Test File.URL"' }).getByLabel("Actions").click();
await page.getByRole("menuitem", { name: "Edit link" }).click();
await page.getByText("Skip confirmation dialog").click();
await page.getByRole("link", { name: "Save" }).click();
await page.getByRole("link", { name: "Test File .URL" }).click();

await page.waitForURL("https://example.org/");
});
});

test.describe("Link File Sharing", () => {
test("open shared link file", async ({ page }) => {
await adminLogin(page);

await page.getByLabel("Files", { exact: true }).click();

await page.getByRole("link", { name: "Test File .webloc" }).scrollIntoViewIfNeeded();
await page
.getByRole("row", { name: 'Toggle selection for file "Test File.webloc" Test File .webloc Show sharing' })
.getByRole("button")
.first()
.click();

await page.getByPlaceholder("Name, email, or Federated").click();
await page.getByPlaceholder("Name, email, or Federated").fill("testuser");
await page.getByRole("option", { name: "testuser-" }).click();
await page.getByRole("button", { name: "Save share" }).click();
await page.getByLabel("Copy internal link to").click();
await expect(page.getByText("Link copied✖")).toBeVisible();

// Log out
await page.getByLabel("Settings menu").click();
await page.getByRole("link", { name: "Log out" }).click();

await testUserLogin(page);

// Go to link copied to clipboard
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
const copiedUrl = await handle.jsonValue();
await page.goto(copiedUrl);

await testFileViewer(page, { fileName: "Test File.webloc", url: "https://example.org", _blank: true });
});

test("edit a shared link file", async ({ page }) => {
await testUserLogin(page);

await page.getByLabel("Files", { exact: true }).click();

await page.getByRole("link", { name: "Test File .webloc" }).scrollIntoViewIfNeeded();
await page.getByRole("link", { name: "Test File .webloc" }).click();

await testFileViewer(page, {
fileName: "Test File.webloc",
url: "https://example.org",
_blank: true,
close: false,
});

await page.getByRole("link", { name: "Edit link" }).click();
await page.getByPlaceholder("e.g. https://example.org").click();
await page.getByPlaceholder("e.g. https://example.org").fill("https://nextcloud.com");
await page.getByPlaceholder("e.g. https://example.org").press("Enter");

await page.getByRole("link", { name: "Test File .webloc" }).click();

await testFileViewer(page, { fileName: "Test File.webloc", url: "https://nextcloud.com", _blank: true });
});
});

test.describe("Public Link File Sharing", () => {
test("open publicly shared link file", async ({ page }) => {
await adminLogin(page);
await page.getByLabel("Files", { exact: true }).click();

// Share the file
await page.getByRole("link", { name: "Test File .URL" }).scrollIntoViewIfNeeded();
await page
.getByRole("row", { name: 'Toggle selection for file "Test File.URL" Test File .URL Show sharing options' })
.getByRole("button")
.first()
.click();

// Create and copy the public link
await page.getByLabel("Create a new share link").click();
await expect(page.getByText("Link copied✖")).toBeVisible();

// Go to link copied to clipboard
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
const copiedUrl = await handle.jsonValue();
await page.goto(copiedUrl);

await page.getByRole("link", { name: "View link" }).click();

await page.waitForURL("https://example.org/");
});

test("cannot edit publicly shared link file", async ({ page }) => {
await adminLogin(page);
await page.getByLabel("Files", { exact: true }).click();

// Share the file
await page.getByRole("link", { name: "Test File .webloc" }).scrollIntoViewIfNeeded();
await page
.getByRole("row", { name: 'Toggle selection for file "Test File.webloc" Test File .webloc Shared Actions' })
.getByLabel("Shared")
.click();

// Create and copy the public link
await page.getByLabel("Create a new share link").click();
await expect(page.getByText("Link copied✖")).toBeVisible();

// Go to link copied to clipboard
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
const copiedUrl = await handle.jsonValue();
await page.goto(copiedUrl);

await page.getByRole("link", { name: "View link" }).click();

await testFileViewer(page, {
fileName: "Test File.webloc",
url: "https://nextcloud.com",
_blank: true,
close: false,
});

await expect(page.getByRole("link", { name: "Edit link" })).not.toBeVisible();
});
});
Loading

0 comments on commit e465a6d

Please sign in to comment.