diff --git a/flake.lock b/flake.lock index 203568a..d052ebf 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1726463316, - "narHash": "sha256-gI9kkaH0ZjakJOKrdjaI/VbaMEo9qBbSUl93DnU7f4c=", + "lastModified": 1729256560, + "narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "99dc8785f6a0adac95f5e2ab05cc2e1bf666d172", + "rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0", "type": "github" }, "original": { diff --git a/rest-api/db/migrations/20240810115846_image_upload_function.sql b/rest-api/db/migrations/20240810115846_image_upload_function.sql index 4c34969..a3e9495 100644 --- a/rest-api/db/migrations/20240810115846_image_upload_function.sql +++ b/rest-api/db/migrations/20240810115846_image_upload_function.sql @@ -6,13 +6,31 @@ AS $$ DECLARE _headers JSON := CURRENT_SETTING('request.headers', TRUE)::JSON; _website_id UUID := (_headers ->> 'x-website-id')::UUID; - _mimetype TEXT := _headers ->> 'x-mimetype'; _original_filename TEXT := _headers ->> 'x-original-filename'; _allowed_mimetypes TEXT[] := ARRAY['image/png', 'image/jpeg', 'image/webp', 'image/avif', 'image/gif', 'image/svg+xml']; _max_file_size BIGINT := 5 * 1024 * 1024; _has_access BOOLEAN; + _mimetype TEXT; BEGIN _has_access = internal.user_has_website_access (_website_id, 20); + _mimetype := CASE WHEN SUBSTRING($1 FROM 1 FOR 8) = '\x89504E470D0A1A0A'::BYTEA THEN + 'image/png' + WHEN SUBSTRING($1 FROM 1 FOR 3) = '\xFFD8FF'::BYTEA THEN + 'image/jpeg' + WHEN SUBSTRING($1 FROM 1 FOR 4) = '\x52494646'::BYTEA + AND SUBSTRING($1 FROM 9 FOR 4) = '\x57454250'::BYTEA THEN + 'image/webp' + WHEN SUBSTRING($1 FROM 5 FOR 7) = '\x66747970617669'::BYTEA THEN + 'image/avif' + WHEN SUBSTRING($1 FROM 1 FOR 6) = '\x474946383761'::BYTEA + OR SUBSTRING($1 FROM 1 FOR 6) = '\x474946383961'::BYTEA THEN + 'image/gif' + WHEN SUBSTRING($1 FROM 1 FOR 5) = '\x3C3F786D6C'::BYTEA + OR SUBSTRING($1 FROM 1 FOR 4) = '\x3C737667'::BYTEA THEN + 'image/svg+xml' + ELSE + NULL + END; IF OCTET_LENGTH($1) = 0 THEN RAISE invalid_parameter_value USING message = 'No file data was provided'; diff --git a/rest-api/db/migrations/20240911070907_change_log.sql b/rest-api/db/migrations/20240911070907_change_log.sql index aa3c23b..580f03e 100644 --- a/rest-api/db/migrations/20240911070907_change_log.sql +++ b/rest-api/db/migrations/20240911070907_change_log.sql @@ -36,6 +36,7 @@ CREATE FUNCTION internal.track_changes () DECLARE _website_id UUID; _user_id UUID := (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID; + _new_value HSTORE; BEGIN IF (NOT EXISTS ( SELECT @@ -43,7 +44,7 @@ BEGIN FROM internal.user AS u WHERE - u.id = _user_id) OR (to_jsonb (OLD.*) - 'last_modified_at' - 'last_modified_by') = (to_jsonb (NEW.*) - 'last_modified_at' - 'last_modified_by')) THEN + u.id = _user_id) OR REGEXP_REPLACE((to_jsonb (OLD.*) - 'last_modified_at' - 'last_modified_by')::TEXT, '\r\n|\r', '\n', 'g') = REGEXP_REPLACE((to_jsonb (NEW.*) - 'last_modified_at' - 'last_modified_by')::TEXT, '\r\n|\r', '\n', 'g')) THEN RETURN NULL; END IF; IF TG_TABLE_NAME = 'website' THEN @@ -52,8 +53,13 @@ BEGIN _website_id := COALESCE(NEW.website_id, OLD.website_id); END IF; IF TG_OP = 'INSERT' THEN + _new_value := CASE WHEN TG_TABLE_NAME = 'media' THEN + HSTORE (NEW) - 'blob'::TEXT + ELSE + HSTORE (NEW) + END; INSERT INTO internal.change_log (website_id, table_name, operation, new_value) - VALUES (_website_id, TG_TABLE_NAME, TG_OP, HSTORE (NEW)); + VALUES (_website_id, TG_TABLE_NAME, TG_OP, _new_value); ELSIF (TG_OP = 'UPDATE' AND EXISTS ( SELECT @@ -86,6 +92,11 @@ CREATE TRIGGER track_changes_website FOR EACH ROW EXECUTE FUNCTION internal.track_changes (); +CREATE TRIGGER track_changes_media + AFTER INSERT ON internal.media + FOR EACH ROW + EXECUTE FUNCTION internal.track_changes (); + CREATE TRIGGER track_changes_settings AFTER UPDATE ON internal.settings FOR EACH ROW @@ -129,6 +140,8 @@ CREATE TRIGGER track_changes_collab -- migrate:down DROP TRIGGER track_changes_website ON internal.website; +DROP TRIGGER track_changes_media ON internal.media; + DROP TRIGGER track_changes_settings ON internal.settings; DROP TRIGGER track_changes_header ON internal.header; diff --git a/rest-api/db/migrations/20241011092744_filesystem_triggers.sql b/rest-api/db/migrations/20241011092744_filesystem_triggers.sql index 0369006..7c396f5 100644 --- a/rest-api/db/migrations/20241011092744_filesystem_triggers.sql +++ b/rest-api/db/migrations/20241011092744_filesystem_triggers.sql @@ -5,27 +5,29 @@ CREATE FUNCTION internal.cleanup_filesystem () DECLARE _website_id UUID; _domain_prefix VARCHAR(16); + _base_path CONSTANT TEXT := '/var/www/archtika-websites/'; + _preview_path TEXT; + _prod_path TEXT; BEGIN IF TG_TABLE_NAME = 'website' THEN _website_id := OLD.id; - SELECT - d.prefix INTO _domain_prefix - FROM - internal.domain_prefix AS d - WHERE - d.website_id = _website_id; - EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/previews/%s''', _website_id); - EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/%s''', COALESCE(_domain_prefix, _website_id::VARCHAR)); ELSE _website_id := OLD.website_id; - SELECT - d.prefix INTO _domain_prefix - FROM - internal.domain_prefix AS d - WHERE - d.website_id = _website_id; - EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/previews/%s/legal-information.html''', _website_id); - EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/%s/legal-information.html''', COALESCE(_domain_prefix, _website_id::VARCHAR)); + END IF; + SELECT + d.prefix INTO _domain_prefix + FROM + internal.domain_prefix d + WHERE + d.website_id = _website_id; + _preview_path := _base_path || 'previews/' || _website_id; + _prod_path := _base_path || COALESCE(_domain_prefix, _website_id::TEXT); + IF TG_TABLE_NAME = 'website' THEN + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf %s''', _preview_path); + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf %s''', _prod_path); + ELSE + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -f %s/legal-information.html''', _preview_path); + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -f %s/legal-information.html''', _prod_path); END IF; RETURN OLD; END; diff --git a/web-app/package-lock.json b/web-app/package-lock.json index ac3a952..7ef462a 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -15,7 +15,7 @@ "marked-highlight": "2.1.4" }, "devDependencies": { - "@playwright/test": "1.46.0", + "@playwright/test": "1.47.0", "@sveltejs/adapter-auto": "3.2.5", "@sveltejs/adapter-node": "5.2.3", "@sveltejs/kit": "2.5.28", @@ -765,13 +765,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz", - "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.0.tgz", + "integrity": "sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.46.0" + "playwright": "1.47.0" }, "bin": { "playwright": "cli.js" @@ -3655,13 +3655,13 @@ } }, "node_modules/playwright": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz", - "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.0.tgz", + "integrity": "sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.46.0" + "playwright-core": "1.47.0" }, "bin": { "playwright": "cli.js" @@ -3674,9 +3674,9 @@ } }, "node_modules/playwright-core": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz", - "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.0.tgz", + "integrity": "sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/web-app/package.json b/web-app/package.json index 36386a7..8715dbd 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -14,7 +14,7 @@ "gents": "pg-to-ts generate -c postgres://postgres@localhost:15432/archtika -o src/lib/db-schema.ts -s internal" }, "devDependencies": { - "@playwright/test": "1.46.0", + "@playwright/test": "1.47.0", "@sveltejs/adapter-auto": "3.2.5", "@sveltejs/adapter-node": "5.2.3", "@sveltejs/kit": "2.5.28", diff --git a/web-app/playwright.config.ts b/web-app/playwright.config.ts index ff4cb3d..436b260 100644 --- a/web-app/playwright.config.ts +++ b/web-app/playwright.config.ts @@ -9,15 +9,36 @@ const config: PlaywrightTestConfig = { baseURL: "http://localhost:4173", video: "retain-on-failure" }, - testDir: "tests", + testDir: "./tests", testMatch: /(.+\.)?(test|spec)\.ts/, - retries: 3, - // Firefox and Webkit are not packaged yet, see https://github.com/NixOS/nixpkgs/issues/288826 + // https://github.com/NixOS/nixpkgs/issues/288826 projects: [ + { + name: "Register users", + testMatch: /global-setup\.ts/, + teardown: "Delete users" + }, + { + name: "Delete users", + testMatch: /global-teardown\.ts/ + }, { name: "Chromium", - use: { ...devices["Desktop Chrome"] } + use: { ...devices["Desktop Chrome"] }, + dependencies: ["Register users"] + }, + { + name: "Firefox", + use: { ...devices["Desktop Firefox"] }, + dependencies: ["Register users"] } + /* + Upstream bug "Error: browserContext.newPage: Target page, context or browser has been closed" + { + name: "Webkit", + use: { ...devices["Desktop Safari"] }, + dependencies: ["Register users"] + } */ ] }; diff --git a/web-app/src/routes/(anonymous)/login/+page.svelte b/web-app/src/routes/(anonymous)/login/+page.svelte index bb9d470..3fae47c 100644 --- a/web-app/src/routes/(anonymous)/login/+page.svelte +++ b/web-app/src/routes/(anonymous)/login/+page.svelte @@ -25,5 +25,5 @@ - + diff --git a/web-app/src/routes/(anonymous)/register/+page.svelte b/web-app/src/routes/(anonymous)/register/+page.svelte index 5939ad5..5e1262a 100644 --- a/web-app/src/routes/(anonymous)/register/+page.svelte +++ b/web-app/src/routes/(anonymous)/register/+page.svelte @@ -52,7 +52,7 @@ - +
diff --git a/web-app/src/routes/(authenticated)/+page.svelte b/web-app/src/routes/(authenticated)/+page.svelte index ec355b1..c661afb 100644 --- a/web-app/src/routes/(authenticated)/+page.svelte +++ b/web-app/src/routes/(authenticated)/+page.svelte @@ -39,7 +39,7 @@ - + @@ -72,7 +72,7 @@ > - +
@@ -105,7 +105,7 @@ > - + {#if website.length > 0} @@ -92,7 +92,7 @@ value={max_storage_size} /> - + diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts index f9d0614..a199a04 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts @@ -43,7 +43,6 @@ export const actions: Actions = { }; if (faviconFile) { - headers["X-Mimetype"] = faviconFile.type; headers["X-Original-Filename"] = faviconFile.name; } @@ -84,7 +83,6 @@ export const actions: Actions = { }; if (logoImage) { - headers["X-Mimetype"] = logoImage.type; headers["X-Original-Filename"] = logoImage.name; } @@ -152,7 +150,6 @@ export const actions: Actions = { "Content-Type": "application/octet-stream", Accept: "application/vnd.pgrst.object+json", "X-Website-Id": params.websiteId, - "X-Mimetype": file.type, "X-Original-Filename": file.name }, body: await file.arrayBuffer(), diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte index 93bab0a..efd156d 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte @@ -95,7 +95,7 @@ {/if} - + @@ -142,7 +142,7 @@ {/if} - + @@ -165,7 +165,7 @@ content={data.home.main_content} /> - + @@ -182,7 +182,7 @@ > - + diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte index a2ad178..7af5c50 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte @@ -40,7 +40,7 @@ - + @@ -74,7 +74,7 @@ > - + diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts index becc990..dc906da 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts @@ -40,7 +40,6 @@ export const actions: Actions = { }; if (coverFile) { - headers["X-Mimetype"] = coverFile.type; headers["X-Original-Filename"] = coverFile.name; } @@ -82,7 +81,6 @@ export const actions: Actions = { "Content-Type": "application/octet-stream", Accept: "application/vnd.pgrst.object+json", "X-Website-Id": params.websiteId, - "X-Mimetype": file.type, "X-Original-Filename": file.name }, body: await file.arrayBuffer(), diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte index 188f6e3..0e37219 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte @@ -119,7 +119,7 @@ content={data.article.main_content ?? ""} /> - + diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/categories/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/categories/+page.svelte index 038e1d6..5b8b0ef 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/categories/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/categories/+page.svelte @@ -44,7 +44,7 @@ - + diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/collaborators/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/collaborators/+page.svelte index 6c872c8..12b95b1 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/collaborators/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/collaborators/+page.svelte @@ -51,7 +51,9 @@ - + diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.server.ts index 16828f8..02aa560 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.server.ts @@ -68,7 +68,6 @@ export const actions: Actions = { "Content-Type": "application/octet-stream", Accept: "application/vnd.pgrst.object+json", "X-Website-Id": params.websiteId, - "X-Mimetype": file.type, "X-Original-Filename": file.name }, body: await file.arrayBuffer(), diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.svelte index e775a9a..d87a7db 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/legal-information/+page.svelte @@ -61,7 +61,9 @@ content={data.legalInformation?.main_content ?? ""} /> - + {#if data.legalInformation?.main_content} diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte index bd8a6ff..9e1a3c9 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte @@ -19,13 +19,13 @@ if (data.website.content_type === "Blog") { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { user, change_log, media, docs_category, ...restTables } = tables; + const { user, change_log, docs_category, ...restTables } = tables; resources = restTables; } if (data.website.content_type === "Docs") { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { user, change_log, media, ...restTables } = tables; + const { user, change_log, ...restTables } = tables; resources = restTables; } @@ -96,7 +96,7 @@ - +
diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte index 1409fa2..27f545f 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte @@ -36,7 +36,9 @@ be published on the Internet.

- +
@@ -73,7 +75,9 @@ required /> - + {#if data.websiteOverview.domain_prefix?.prefix} diff --git a/web-app/template-styles/common-styles.css b/web-app/template-styles/common-styles.css index c7b1b66..ea4eeca 100644 --- a/web-app/template-styles/common-styles.css +++ b/web-app/template-styles/common-styles.css @@ -32,6 +32,7 @@ input, button, textarea, select, +input[type="file"]::file-selector-button, a[role="button"], label[for="toggle-mobile-preview"], label[for="toggle-sidebar"], @@ -59,6 +60,11 @@ input[type="file"] { inline-size: 100%; } +input[type="file"]::file-selector-button { + padding-block: calc(var(--space-3xs) / 4); + margin-inline-end: var(--space-2xs); +} + input[type="color"] { padding: 0; } @@ -73,6 +79,7 @@ summary { } button, +input[type="file"]::file-selector-button, a[role="button"], label[for="toggle-mobile-preview"], label[for="toggle-sidebar"], @@ -226,12 +233,14 @@ pre { flex-shrink: 0; } -code { +code, +kbd { font-family: monospace; font-size: var(--font-size--1); } -:not(pre) > code { +:not(pre) > code, +kbd { background-color: var(--bg-secondary); border: var(--border-primary); padding-inline: var(--space-3xs); @@ -278,6 +287,16 @@ del { color: var(--color-text-invert); } +blockquote { + border-inline-start: var(--border-primary); + border-width: 0.25rem; + padding-inline-start: var(--space-xs); +} + +meter { + inline-size: min(512px, 100%); +} + .hljs { color: var(--hl-color); background: var(--hl-bg); diff --git a/web-app/tests/account.spec.ts b/web-app/tests/account.spec.ts index 5788343..15d1c6c 100644 --- a/web-app/tests/account.spec.ts +++ b/web-app/tests/account.spec.ts @@ -1,55 +1,25 @@ -import { test as base, expect, type Page } from "@playwright/test"; -import { randomBytes } from "node:crypto"; +import { test, expect } from "@playwright/test"; +import { userOwner, register, authenticate, password } from "./shared"; -const username = randomBytes(8).toString("hex"); -const password = "T3stuser??!!"; +const userDeleted = "test-deleted-a"; -const test = base.extend<{ authenticatedPage: Page }>({ - authenticatedPage: async ({ page }, use) => { - await page.goto("/login"); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await use(page); - } +test(`Logout`, async ({ page }) => { + await authenticate(userOwner, page); + await page.getByRole("link", { name: "Account" }).click(); + await page.getByRole("button", { name: "Logout" }).click(); + await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); }); -test.describe.serial("Account tests", () => { - test("Register", async ({ page }) => { - await page.goto("/register"); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully registered, you")).toBeVisible(); - }); - - test("Login", async ({ page }) => { - await page.goto("/login"); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible(); - }); - - test("Logout", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Logout" }).click(); - await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); - }); - - test("Delete account", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); - }); +test(`Delete account`, async ({ page }) => { + await register(userDeleted, page); + await authenticate(userDeleted, page); + await page.getByRole("link", { name: "Account" }).click(); + await page.getByRole("button", { name: "Delete account" }).click(); + await page.getByLabel("Password:").click(); + await page.getByLabel("Password:").fill(password); + await page + .locator("#delete-account-modal") + .getByRole("button", { name: "Delete account" }) + .click(); + await expect(page.getByRole("heading", { name: "Login" })).toBeVisible(); }); diff --git a/web-app/tests/articles.spec.ts b/web-app/tests/articles.spec.ts new file mode 100644 index 0000000..c5989eb --- /dev/null +++ b/web-app/tests/articles.spec.ts @@ -0,0 +1,151 @@ +import { test, expect } from "@playwright/test"; +import { + userOwner, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite +} from "./shared"; + +test.describe("Website owner", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Articles" }).click(); + }); + + test(`Create article`, async ({ page }) => { + await page.getByRole("button", { name: "Create article" }).click(); + await page.locator("#create-article-modal").getByLabel("Title:").click(); + await page.locator("#create-article-modal").getByLabel("Title:").fill("Article"); + await page + .locator("#create-article-modal") + .getByRole("button", { name: "Create article" }) + .click(); + await expect(page.getByText("Successfully created article")).toBeVisible(); + await expect(page.getByRole("link", { name: "All articles" })).toBeVisible(); + }); + + test.describe("Modify", () => { + test.beforeEach(async ({ page }) => { + await page.getByRole("button", { name: "Create article" }).click(); + await page.locator("#create-article-modal").getByLabel("Title:").click(); + await page.locator("#create-article-modal").getByLabel("Title:").fill("Article"); + await page + .locator("#create-article-modal") + .getByRole("button", { name: "Create article" }) + .click(); + }); + + test(`Update article`, async ({ page }) => { + await page.getByRole("link", { name: "Edit" }).first().click(); + await page.getByLabel("Weight:").click(); + await page.getByLabel("Weight:").fill("555"); + await page.getByLabel("Title:").click(); + await page.getByLabel("Title:").press("ControlOrMeta+a"); + await page.getByLabel("Title:").fill("Example article"); + await page.getByLabel("Description:").click(); + await page.getByLabel("Description:").fill("Random description"); + await page.getByLabel("Author:").click(); + await page.getByLabel("Author:").fill("John Doe"); + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").fill("## Markdown content comes here"); + await page.getByRole("button", { name: "Update article" }).click(); + await expect(page.getByText("Successfully updated article")).toBeVisible(); + }); + + test(`Delete article`, async ({ page }) => { + await page.getByRole("button", { name: "Delete" }).first().click(); + await page.getByRole("button", { name: "Delete article" }).click(); + await expect(page.getByText("Successfully deleted article")).toBeVisible(); + }); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test.beforeEach(async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Articles" }).click(); + }); + + test(`Create article`, async ({ page }) => { + await page.getByRole("button", { name: "Create article" }).click(); + await page.locator("#create-article-modal").getByLabel("Title:").click(); + await page.locator("#create-article-modal").getByLabel("Title:").fill("Article"); + await page + .locator("#create-article-modal") + .getByRole("button", { name: "Create article" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page + .locator("#create-article-modal") + .getByRole("button", { name: "Create article" }) + .click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully created article")).toBeVisible(); + await expect(page.getByRole("link", { name: "All articles" })).toBeVisible(); + } + }); + + test(`Update article`, async ({ page }) => { + await page.getByRole("link", { name: "Edit" }).first().click(); + await page.getByLabel("Weight:").click(); + await page.getByLabel("Weight:").fill("555"); + await page.getByLabel("Title:").click(); + await page.getByLabel("Title:").press("ControlOrMeta+a"); + await page.getByLabel("Title:").fill("Example article"); + await page.getByLabel("Description:").click(); + await page.getByLabel("Description:").fill("Random description"); + await page.getByLabel("Author:").click(); + await page.getByLabel("Author:").fill("John Doe"); + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").fill("## Markdown content comes here"); + await page + .getByRole("button", { name: "Update article" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update article" }).click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated article")).toBeVisible(); + } + }); + + test(`Delete article`, async ({ page }) => { + await page.getByRole("button", { name: "Delete" }).first().click(); + await page + .getByRole("button", { name: "Delete article" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Delete article" }).click(); + + switch (permissionLevel) { + case 10: + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + break; + case 20: + await expect( + page + .getByText("Successfully deleted article") + .or(page.getByText("Insufficient permissions")) + ).toBeVisible(); + break; + case 30: + await expect(page.getByText("Successfully deleted article")).toBeVisible(); + break; + } + }); + }); +} diff --git a/web-app/tests/categories.spec.ts b/web-app/tests/categories.spec.ts new file mode 100644 index 0000000..60886e0 --- /dev/null +++ b/web-app/tests/categories.spec.ts @@ -0,0 +1,149 @@ +import { test, expect } from "@playwright/test"; +import { randomBytes, randomInt } from "node:crypto"; +import { + userOwner, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite +} from "./shared"; + +const genCategoryName = () => randomBytes(12).toString("hex"); +const genCategoryWeight = (min = 10, max = 10000) => randomInt(min, max).toString(); + +test.describe("Website owner", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Categories" }).click(); + }); + + test(`Create category`, async ({ page }) => { + await page.getByRole("button", { name: "Create category" }).click(); + await page.locator("#create-category-modal").getByLabel("Name:").click(); + await page.locator("#create-category-modal").getByLabel("Name:").fill(genCategoryName()); + await page.locator("#create-category-modal").getByLabel("Weight:").click(); + await page.locator("#create-category-modal").getByLabel("Weight:").fill(genCategoryWeight()); + await page + .locator("#create-category-modal") + .getByRole("button", { name: "Create category" }) + .click(); + await expect(page.getByText("Successfully created category")).toBeVisible(); + await expect(page.getByRole("link", { name: "All categories" })).toBeVisible(); + }); + + test.describe("Modify", () => { + test.beforeEach(async ({ page }) => { + await page.getByRole("button", { name: "Create category" }).click(); + await page.locator("#create-category-modal").getByLabel("Name:").click(); + await page.locator("#create-category-modal").getByLabel("Name:").fill(genCategoryName()); + await page.locator("#create-category-modal").getByLabel("Weight:").click(); + await page.locator("#create-category-modal").getByLabel("Weight:").fill(genCategoryWeight()); + await page + .locator("#create-category-modal") + .getByRole("button", { name: "Create category" }) + .click(); + }); + + test(`Update category`, async ({ page }) => { + await page.getByRole("button", { name: "Update" }).first().click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).getByLabel("Name:").click(); + await page.locator(`#${modalName}`).getByLabel("Name:").fill(genCategoryName()); + await page.locator(`#${modalName}`).getByLabel("Weight:").click(); + await page.locator(`#${modalName}`).getByLabel("Weight:").fill(genCategoryWeight()); + await page.getByRole("button", { name: "Update category" }).click(); + await expect(page.getByText("Successfully updated category")).toBeVisible(); + }); + + test(`Delete category`, async ({ page }) => { + await page.getByRole("button", { name: "Delete" }).first().click(); + await page.getByRole("button", { name: "Delete category" }).click(); + await expect(page.getByText("Successfully deleted category")).toBeVisible(); + }); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test.beforeEach(async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Categories" }).click(); + }); + + test(`Create category`, async ({ page }) => { + await page.getByRole("button", { name: "Create category" }).click(); + await page.locator("#create-category-modal").getByLabel("Name:").click(); + await page.locator("#create-category-modal").getByLabel("Name:").fill(genCategoryName()); + await page.locator("#create-category-modal").getByLabel("Weight:").click(); + await page.locator("#create-category-modal").getByLabel("Weight:").fill(genCategoryWeight()); + await page + .locator("#create-category-modal") + .getByRole("button", { name: "Create category" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page + .locator("#create-category-modal") + .getByRole("button", { name: "Create category" }) + .click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully created category")).toBeVisible(); + await expect(page.getByRole("link", { name: "All categories" })).toBeVisible(); + } + }); + + test(`Update category`, async ({ page }) => { + await page.getByRole("button", { name: "Update" }).first().click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).getByLabel("Name:").click(); + await page.locator(`#${modalName}`).getByLabel("Name:").fill(genCategoryName()); + await page.locator(`#${modalName}`).getByLabel("Weight:").click(); + await page.locator(`#${modalName}`).getByLabel("Weight:").fill(genCategoryWeight()); + await page + .getByRole("button", { name: "Update category" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update category" }).click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated category")).toBeVisible(); + } + }); + + test(`Delete category`, async ({ page }) => { + await page.getByRole("button", { name: "Delete" }).first().click(); + await page + .getByRole("button", { name: "Delete category" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Delete category" }).click(); + + switch (permissionLevel) { + case 10: + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + break; + case 20: + await expect( + page + .getByText("Successfully deleted category") + .or(page.getByText("Insufficient permissions")) + ).toBeVisible(); + break; + case 30: + await expect(page.getByText("Successfully deleted category")).toBeVisible(); + break; + } + }); + }); +} diff --git a/web-app/tests/collaborator.spec.ts b/web-app/tests/collaborator.spec.ts deleted file mode 100644 index fe75950..0000000 --- a/web-app/tests/collaborator.spec.ts +++ /dev/null @@ -1,622 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { randomBytes } from "node:crypto"; -import { platform } from "node:os"; - -const username = randomBytes(8).toString("hex"); -const collabUsername = randomBytes(8).toString("hex"); -const collabUsername2 = randomBytes(8).toString("hex"); -const collabUsername3 = randomBytes(8).toString("hex"); -const collabUsername4 = randomBytes(8).toString("hex"); -const customPrefix = Buffer.from(randomBytes(16).map((byte) => (byte % 26) + 97)).toString(); -const customPrefix2 = Buffer.from(randomBytes(16).map((byte) => (byte % 26) + 97)).toString(); -const password = "T3stuser??!!"; - -const permissionLevels = [10, 20, 30]; - -test.describe.serial("Collaborator tests", () => { - test("Setup", async ({ page }) => { - await page.goto("/register"); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername2); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername3); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername4); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.goto("/login"); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByRole("button", { name: "Create website" }).click(); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Blog"); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByRole("button", { name: "Publish" }).click(); - await page.getByLabel("Prefix:").click(); - await page.getByLabel("Prefix:").fill(customPrefix); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.goto("/"); - await page.getByRole("button", { name: "Create website" }).click(); - await page.getByLabel("Type: BlogDocs").selectOption("Docs"); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Documentation"); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("button", { name: "Create article" }).click(); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Article-10"); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByRole("link", { name: "Collaborators" }).click(); - await page.getByRole("button", { name: "Add collaborator" }).click(); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("button", { name: "Add collaborator" }).click(); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername2); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("button", { name: "Add collaborator" }).click(); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername3); - await page.getByRole("combobox").selectOption("30"); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.goto("/"); - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page.getByRole("button", { name: "Create category" }).click(); - await page.getByLabel("Name:").nth(0).click(); - await page.getByLabel("Name:").nth(0).fill("Category-10"); - await page.getByLabel("Weight:").click(); - await page.getByLabel("Weight:").fill("10"); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByRole("link", { name: "Collaborators" }).click(); - await page.getByRole("button", { name: "Add collaborator" }).click(); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByRole("link", { name: "Legal information" }).click(); - await page.getByLabel("Main content:").click(); - await page.getByLabel("Main content:").fill("## Content"); - await page.getByRole("button", { name: "Submit" }).click(); - }); - - for (const permissionLevel of permissionLevels) { - test(`Set collaborator permission level to ${permissionLevel}`, async ({ page }) => { - await page.goto("/login"); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page - .locator("li") - .filter({ hasText: collabUsername }) - .getByRole("button") - .first() - .click(); - await page.getByRole("combobox").selectOption(permissionLevel.toString()); - await page.getByRole("button", { name: "Update collaborator" }).click(); - - await page.goto("/"); - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page - .locator("li") - .filter({ hasText: collabUsername }) - .getByRole("button") - .first() - .click(); - await page.getByRole("combobox").selectOption(permissionLevel.toString()); - await page.getByRole("button", { name: "Update collaborator" }).click(); - }); - - test.describe.serial(`Permission level: ${permissionLevel}`, () => { - test.beforeEach(async ({ page }) => { - await page.goto("/login"); - await page.getByLabel("Username:").fill(collabUsername); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - }); - - test("Update website", async ({ page }) => { - await page.locator("li").filter({ hasText: "Blog" }).getByRole("button").first().click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated website")).toBeVisible(); - } - }); - test("Delete website", async ({ page }) => { - await page.locator("li").filter({ hasText: "Blog" }).getByRole("button").nth(1).click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete website" }).click(); - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - }); - test("Update Global", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page - .locator('#global button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.locator("#global").getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated global")).toBeVisible(); - } - }); - test("Update Header", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page - .locator('#header button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.locator("#header").getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated header")).toBeVisible(); - } - }); - test("Update Home", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByLabel("Description:").click(); - await page.getByLabel("Description:").fill("Description"); - await page - .locator('#home button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.locator("#home").getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated home")).toBeVisible(); - } - }); - test("Update Footer", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page - .locator('#footer button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.locator("#footer").getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated footer")).toBeVisible(); - } - }); - test("Create article", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("button", { name: "Create article" }).click(); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill(`Article-${permissionLevel}`); - await page - .locator('form[action="?/createArticle"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully created article")).toBeVisible(); - } - }); - test("Update article", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page - .locator("li") - .filter({ hasText: `Article-${permissionLevel}` }) - .getByRole("link") - .click(); - await page.getByLabel("Description:").click(); - await page.getByLabel("Description:").fill("Description"); - await page.getByLabel("Author:").click(); - await page.getByLabel("Author:").fill("Author"); - await page.getByLabel("Main content:").click(); - await page.getByLabel("Main content:").fill("## Main content"); - await page - .locator('form button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated article")).toBeVisible(); - } - }); - test("Delete article", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page - .locator("li") - .filter({ hasText: `Article-${permissionLevel}` }) - .getByRole("button") - .click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete article" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } - if ([20, 30].includes(permissionLevel)) { - await expect(page.getByText("Successfully deleted article")).toBeVisible(); - - await page.locator("li").filter({ hasText: `Article-10` }).getByRole("button").click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete article" }).click(); - - if (permissionLevel === 20) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully deleted article")).toBeVisible(); - } - } - }); - test("Add collaborator", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page.getByRole("button", { name: "Add collaborator" }).click(); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername4); - await page - .locator('form[action="?/addCollaborator"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully added")).toBeVisible(); - } - }); - test("Update collaborator", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page - .locator("li") - .filter({ hasText: collabUsername2 }) - .getByRole("button") - .first() - .click(); - await page.getByRole("combobox").selectOption("20"); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Update collaborator" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated")).toBeVisible(); - - await page - .locator("li") - .filter({ hasText: collabUsername2 }) - .getByRole("button") - .first() - .click(); - await page.getByRole("combobox").selectOption("30"); - await page.getByRole("button", { name: "Update collaborator" }).click(); - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } - }); - test("Remove collaborator", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page - .locator("li") - .filter({ hasText: collabUsername2 }) - .getByRole("button") - .nth(1) - .click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Remove collaborator" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully removed")).toBeVisible(); - - await page - .locator("li") - .filter({ hasText: collabUsername3 }) - .getByRole("button") - .nth(1) - .click(); - await page.getByRole("button", { name: "Remove collaborator" }).click(); - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } - }); - test("Create/Update legal information", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Legal information" }).click(); - await page.getByLabel("Main content:").click(); - await page.getByLabel("Main content:").fill("## Content"); - await page - .locator('form[action="?/createUpdateLegalInformation"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 30) { - await expect(page.getByText("Successfully created/updated legal")).toBeVisible(); - } else { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } - }); - test("Delete legal information", async ({ page }) => { - await page - .getByRole("link", { - name: [10, 20].includes(permissionLevel) ? "Documentation" : "Blog" - }) - .click(); - await page.getByRole("link", { name: "Legal information" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page - .locator('form[action="?/deleteLegalInformation"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete legal information" }).click(); - - if (permissionLevel === 30) { - await expect(page.getByText("Successfully deleted legal")).toBeVisible(); - } else { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } - }); - test("Create category", async ({ page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page.getByRole("button", { name: "Create category" }).click(); - await page.getByLabel("Name:").nth(0).click(); - await page.getByLabel("Name:").nth(0).fill(`Category-${permissionLevel}`); - await page.getByRole("spinbutton", { name: "Weight:" }).click(); - await page.getByRole("spinbutton", { name: "Weight:" }).fill(permissionLevel.toString()); - await page - .locator('form[action="?/createCategory"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully created category")).toBeVisible(); - } - }); - test("Update category", async ({ page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page - .locator("li") - .filter({ hasText: `Category-${permissionLevel}` }) - .getByRole("button") - .first() - .click(); - await page.getByRole("spinbutton", { name: "Weight:" }).click(); - await page - .getByRole("spinbutton", { name: "Weight:" }) - .fill((permissionLevel * 2).toString()); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Update category" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully updated category")).toBeVisible(); - } - }); - test("Delete category", async ({ page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page - .locator("li") - .filter({ hasText: `Category-${permissionLevel}` }) - .getByRole("button") - .nth(1) - .click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete category" }).click(); - - if (permissionLevel === 10) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } - if ([20, 30].includes(permissionLevel)) { - await expect(page.getByText("Successfully deleted category")).toBeVisible(); - - await page - .locator("li") - .filter({ hasText: "Category-10" }) - .getByRole("button") - .nth(1) - .click(); - const modalName = page.url().split("#")[1]; - await page - .locator(`div[id="${modalName}"] button[type="submit"]`) - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete category" }).click(); - - if (permissionLevel === 20) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully deleted category")).toBeVisible(); - } - } - }); - test("Publish website", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page - .locator('form[action="?/publishWebsite"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Publish" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully published website")).toBeVisible(); - } - }); - test("Set custom domain prefix", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - - const isMac = platform() === "darwin"; - const modifier = isMac ? "Meta" : "Control"; - await page.getByLabel("Prefix:").click(); - await page.keyboard.press(`${modifier}+A`); - await page.keyboard.press(`Backspace`); - await page.getByLabel("Prefix:").fill(customPrefix2); - await page - .locator('form[action="?/createUpdateCustomDomainPrefix"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Submit" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully created/updated")).toBeVisible(); - } - }); - test("Remove custom domain prefix", async ({ page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page - .locator('form[action="?/deleteCustomDomainPrefix"] button[type="submit"]') - .evaluate((node) => node.removeAttribute("disabled")); - await page.getByRole("button", { name: "Delete domain prefix" }).click(); - - if ([10, 20].includes(permissionLevel)) { - await expect(page.getByText("Insufficient permissions")).toBeVisible(); - } else { - await expect(page.getByText("Successfully deleted domain")).toBeVisible(); - } - }); - }); - } - - test("Delete all accounts", async ({ page }) => { - await page.goto("/login"); - - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - - await page.getByLabel("Username:").fill(collabUsername); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - - await page.getByLabel("Username:").fill(collabUsername2); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - - await page.getByLabel("Username:").fill(collabUsername3); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - - await page.getByLabel("Username:").fill(collabUsername4); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - }); -}); diff --git a/web-app/tests/collaborators.spec.ts b/web-app/tests/collaborators.spec.ts new file mode 100644 index 0000000..389c077 --- /dev/null +++ b/web-app/tests/collaborators.spec.ts @@ -0,0 +1,209 @@ +import { test, expect } from "@playwright/test"; +import { randomBytes, randomInt, type UUID } from "node:crypto"; +import { + userOwner, + register, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite, + userCollab10, + userCollab20, + userCollab30 +} from "./shared"; + +const genUsername = () => randomBytes(8).toString("hex") as UUID; +const pickPermissionLevel = () => permissionLevels[randomInt(permissionLevels.length)].toString(); + +test.describe("Website owner", () => { + test(`Add collaborator`, async ({ page }) => { + const addUsername = genUsername(); + + await register(addUsername, page); + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Collaborators" }).click(); + + await page.getByRole("button", { name: "Add collaborator" }).click(); + await page.locator("#add-collaborator-modal").getByLabel("Username:").click(); + await page.locator("#add-collaborator-modal").getByLabel("Username:").fill(addUsername); + await page + .locator("#add-collaborator-modal") + .getByLabel("Permission level:") + .selectOption(pickPermissionLevel()); + await page + .locator("#add-collaborator-modal") + .getByRole("button", { name: "Add collaborator" }) + .click(); + await expect(page.getByText("Successfully added collaborator")).toBeVisible(); + await expect(page.getByRole("link", { name: "All collaborators" })).toBeVisible(); + }); + + test.describe("Modify", () => { + let modifyUsername: UUID; + + test.beforeEach(async ({ page }) => { + modifyUsername = genUsername(); + await register(modifyUsername, page); + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Collaborators" }).click(); + await page.getByRole("button", { name: "Add collaborator" }).click(); + await page.locator("#add-collaborator-modal").getByLabel("Username:").click(); + await page.locator("#add-collaborator-modal").getByLabel("Username:").fill(modifyUsername); + await page + .locator("#add-collaborator-modal") + .getByLabel("Permission level:") + .selectOption(pickPermissionLevel()); + await page + .locator("#add-collaborator-modal") + .getByRole("button", { name: "Add collaborator" }) + .click(); + }); + + test(`Update collaborator`, async ({ page }) => { + await page + .locator("li") + .filter({ hasText: modifyUsername }) + .getByRole("button", { name: "Update" }) + .first() + .click(); + const modalName = page.url().split("#")[1]; + await page + .locator(`#${modalName}`) + .getByLabel("Permission level:") + .selectOption(pickPermissionLevel()); + await page.getByRole("button", { name: "Update collaborator" }).click(); + await expect(page.getByText("Successfully updated collaborator")).toBeVisible(); + }); + + test(`Remove collaborator`, async ({ page }) => { + await page + .locator("li") + .filter({ hasText: modifyUsername }) + .getByRole("button", { name: "Remove" }) + .first() + .click(); + await page.getByRole("button", { name: "Remove collaborator" }).click(); + await expect(page.getByText("Successfully removed collaborator")).toBeVisible(); + }); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test(`Add collaborator`, async ({ page }) => { + const addUsername = genUsername(); + + await register(addUsername, page); + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Collaborators" }).click(); + + await page.getByRole("button", { name: "Add collaborator" }).click(); + await page.locator("#add-collaborator-modal").getByLabel("Username:").click(); + await page.locator("#add-collaborator-modal").getByLabel("Username:").fill(addUsername); + await page + .locator("#add-collaborator-modal") + .getByLabel("Permission level:") + .selectOption(pickPermissionLevel()); + await page + .locator("#add-collaborator-modal") + .getByRole("button", { name: "Add collaborator" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page + .locator("#add-collaborator-modal") + .getByRole("button", { name: "Add collaborator" }) + .click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect( + page + .getByText("Successfully added collaborator") + .or(page.getByText("Insufficient permissions")) + ).toBeVisible(); + } + }); + + test(`Update collaborator`, async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Collaborators" }).click(); + + await page + .locator("li") + .filter({ hasNotText: new RegExp(`${userCollab10}|${userCollab20}|${userCollab30}`) }) + .getByRole("button", { name: "Update" }) + .first() + .click(); + const modalName = page.url().split("#")[1]; + await page + .locator(`#${modalName}`) + .getByLabel("Permission level:") + .selectOption(pickPermissionLevel()); + await page + .getByRole("button", { name: "Update collaborator" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update collaborator" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect( + page + .getByText("Successfully updated collaborator") + .or(page.getByText("Insufficient permissions")) + ).toBeVisible(); + } + }); + + test(`Remove collaborator`, async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Collaborators" }).click(); + + await page + .locator("li") + .filter({ hasNotText: new RegExp(`${userCollab10}|${userCollab20}|${userCollab30}`) }) + .getByRole("button", { name: "Remove" }) + .first() + .click(); + await page + .getByRole("button", { name: "Remove collaborator" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Remove collaborator" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect( + page + .getByText("Successfully removed collaborator") + .or(page.getByText("Insufficient permissions")) + ).toBeVisible(); + } + }); + }); +} diff --git a/web-app/tests/dashboard.spec.ts b/web-app/tests/dashboard.spec.ts new file mode 100644 index 0000000..53b8952 --- /dev/null +++ b/web-app/tests/dashboard.spec.ts @@ -0,0 +1,106 @@ +import { test, expect } from "@playwright/test"; +import { + userOwner, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite +} from "./shared"; + +test.describe("Website owner", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + }); + + test(`Create website`, async ({ page }) => { + await page.getByRole("button", { name: "Create website" }).click(); + await page.getByLabel("Type:").selectOption("Blog"); + await page.locator("#create-website-modal").getByLabel("Title:").click(); + await page.locator("#create-website-modal").getByLabel("Title:").fill("Blog"); + await page + .locator("#create-website-modal") + .getByRole("button", { name: "Create website" }) + .click(); + const successCreation = page.getByText("Successfully created website"); + const limitExceeded = page.getByText("Limit of 3 websites exceeded"); + await expect(successCreation.or(limitExceeded)).toBeVisible(); + await expect(page.getByRole("link", { name: "All websites" })).toBeVisible(); + }); + + test.describe("Modify", () => { + test.beforeEach(async ({ page }) => { + await page.getByRole("button", { name: "Create website" }).click(); + await page.getByLabel("Type:").selectOption("Blog"); + await page.locator("#create-website-modal").getByLabel("Title:").click(); + await page.locator("#create-website-modal").getByLabel("Title:").fill("Blog"); + await page + .locator("#create-website-modal") + .getByRole("button", { name: "Create website" }) + .click(); + }); + + test(`Update website`, async ({ page }) => { + await page + .locator("li") + .filter({ hasNotText: collabTestingWebsite }) + .getByRole("button", { name: "Update" }) + .first() + .click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).getByLabel("Title:").click(); + await page.locator(`#${modalName}`).getByLabel("Title:").fill(`${"Blog"} updated`); + await page.getByRole("button", { name: "Update website" }).click(); + await expect(page.getByText("Successfully updated website")).toBeVisible(); + }); + test(`Delete website`, async ({ page }) => { + await page + .locator("li") + .filter({ hasNotText: collabTestingWebsite }) + .getByRole("button", { name: "Delete" }) + .first() + .click(); + await page.getByRole("button", { name: "Delete website" }).click(); + await expect(page.getByText("Successfully deleted website")).toBeVisible(); + }); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test.beforeEach(async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + }); + + test("Update website", async ({ page }) => { + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("button", { name: "Update" }) + .click(); + await page + .getByRole("button", { name: "Update website" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update website" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated website")).toBeVisible(); + } + }); + + test("Delete website", async ({ page }) => { + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("button", { name: "Delete" }) + .click(); + await page + .getByRole("button", { name: "Delete website" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Delete website" }).click(); + + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + }); + }); +} diff --git a/web-app/tests/global-setup.ts b/web-app/tests/global-setup.ts new file mode 100644 index 0000000..254b10e --- /dev/null +++ b/web-app/tests/global-setup.ts @@ -0,0 +1,64 @@ +import { test } from "@playwright/test"; +import { + allUsers, + register, + authenticate, + userOwner, + collabTestingWebsite, + permissionLevels, + collabUsers, + userDummy +} from "./shared"; + +for (const username of allUsers) { + test(`Register user "${username}`, async ({ page }) => { + await register(username, page); + }); +} + +test.describe("Collaborator testing website", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + }); + + test("Create website", async ({ page }) => { + await page.getByRole("button", { name: "Create website" }).click(); + await page.getByLabel("Type:").selectOption("Docs"); + await page.locator("#create-website-modal").getByLabel("Title:").click(); + await page.locator("#create-website-modal").getByLabel("Title:").fill(collabTestingWebsite); + await page + .locator("#create-website-modal") + .getByRole("button", { name: "Create website" }) + .click(); + }); + + for (const permissionLevel of permissionLevels) { + test(`Add collaborator "${collabUsers.get(permissionLevel)}"`, async ({ page }) => { + await page.getByRole("link", { name: collabTestingWebsite }).click(); + await page.getByRole("link", { name: "Collaborators" }).click(); + await page.getByRole("button", { name: "Add collaborator" }).click(); + await page.getByLabel("Username:").click(); + await page.getByLabel("Username:").fill(collabUsers.get(permissionLevel)!); + await page + .locator("#add-collaborator-modal") + .getByLabel("Permission level:") + .selectOption(permissionLevel.toString()); + await page + .locator("#add-collaborator-modal") + .getByRole("button", { name: "Add collaborator" }) + .click(); + }); + } +}); + +test("Dummy user website", async ({ page }) => { + await authenticate(userDummy, page); + await page.getByRole("button", { name: "Create website" }).click(); + await page.getByLabel("Type:").selectOption("Blog"); + await page.locator("#create-website-modal").getByLabel("Title:").click(); + await page.locator("#create-website-modal").getByLabel("Title:").fill("Dummy"); + await page + .locator("#create-website-modal") + .getByRole("button", { name: "Create website" }) + .click(); +}); diff --git a/web-app/tests/global-teardown.ts b/web-app/tests/global-teardown.ts new file mode 100644 index 0000000..fae68af --- /dev/null +++ b/web-app/tests/global-teardown.ts @@ -0,0 +1,31 @@ +import { test } from "@playwright/test"; +import { password, authenticate, userOwner } from "./shared"; + +test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); +}); + +/* test("Delete all regular users", async ({ page }) => { + await page.getByRole("link", { name: "Manage" }).click(); + + await page.waitForSelector("tbody"); + const userRows = await page.locator("tbody > tr").filter({ hasNotText: userOwner }).all(); + + for (const row of userRows) { + await row.getByRole("button", { name: "Manage" }).click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).locator('summary:has-text("Delete")').click(); + await page.locator(`#${modalName}`).getByRole("button", { name: "Delete user" }).click(); + } +}); + +test("Delete admin account", async ({ page }) => { + await page.getByRole("link", { name: "Account" }).click(); + await page.getByRole("button", { name: "Delete account" }).click(); + await page.getByLabel("Password:").click(); + await page.getByLabel("Password:").fill(password); + await page + .locator("#delete-account-modal") + .getByRole("button", { name: "Delete account" }) + .click(); +}); */ diff --git a/web-app/tests/legal-information.spec.ts b/web-app/tests/legal-information.spec.ts new file mode 100644 index 0000000..e2e23ea --- /dev/null +++ b/web-app/tests/legal-information.spec.ts @@ -0,0 +1,109 @@ +import { test, expect } from "@playwright/test"; +import { + userOwner, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite +} from "./shared"; + +test.describe("Website owner", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Legal information" }).click(); + }); + + test(`Create/update legal information`, async ({ page }) => { + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").press("ControlOrMeta+a"); + await page.getByLabel("Main content:").fill("## Content"); + await page.getByRole("button", { name: "Update legal information" }).click(); + await expect(page.getByText("Successfully created/updated legal information")).toBeVisible(); + }); + + test(`Delete legal information`, async ({ page }) => { + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").press("ControlOrMeta+a"); + await page.getByLabel("Main content:").fill("## Arbitrary content"); + await page.getByRole("button", { name: "Update legal information" }).click(); + + await page.getByRole("button", { name: "Delete" }).click(); + await page.getByRole("button", { name: "Delete legal information" }).click(); + await expect(page.getByText("Successfully deleted legal information")).toBeVisible(); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test(`Create/update legal information`, async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Legal information" }).click(); + + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").press("ControlOrMeta+a"); + await page.getByLabel("Main content:").fill("## Random content"); + await page + .getByRole("button", { name: "Update legal information" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update legal information" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect( + page.getByText("Successfully created/updated legal information") + ).toBeVisible(); + } + }); + + test(`Delete legal information`, async ({ page, browserName }) => { + test.skip(browserName === "firefox", "Some issues with Firefox in headful mode"); + + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Legal information" }).click(); + + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").press("ControlOrMeta+a"); + await page.getByLabel("Main content:").fill("## Even more content"); + await page.getByRole("button", { name: "Update legal information" }).click(); + await page.waitForResponse(/createUpdateLegalInformation/); + await page.getByRole("link", { name: "Account" }).click(); + await page.getByRole("button", { name: "Logout" }).click(); + + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Legal information" }).click(); + + await page.getByRole("button", { name: "Delete" }).click(); + await page + .getByRole("button", { name: "Delete legal information" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Delete legal information" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully deleted legal information")).toBeVisible(); + } + }); + }); +} diff --git a/web-app/tests/manage.spec.ts b/web-app/tests/manage.spec.ts new file mode 100644 index 0000000..569f8bd --- /dev/null +++ b/web-app/tests/manage.spec.ts @@ -0,0 +1,67 @@ +import { test, expect } from "@playwright/test"; +import { userOwner, userDummy, register, authenticate } from "./shared"; + +const userDeleted = "test-deleted-m"; + +test(`Update user website limit`, async ({ page }) => { + await authenticate(userOwner, page); + await page.getByRole("link", { name: "Manage" }).click(); + await page + .locator("tr") + .filter({ hasNotText: userOwner }) + .getByRole("button", { name: "Manage" }) + .first() + .click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).getByLabel("Number of websites allowed:").click(); + await page.locator(`#${modalName}`).getByLabel("Number of websites allowed:").fill("5"); + await page.getByRole("button", { name: "Update website limit" }).click(); + await expect(page.getByText("Successfully updated user website limit")).toBeVisible(); +}); + +test(`Update user website storage limit`, async ({ page }) => { + await authenticate(userOwner, page); + await page.getByRole("link", { name: "Manage" }).click(); + await page + .locator("tr") + .filter({ hasText: userDummy }) + .getByRole("button", { name: "Manage" }) + .first() + .click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).locator("details > summary").first().click(); + await page + .locator(`#${modalName}`) + .locator("details") + .getByLabel("Storage limit in MB:") + .first() + .click(); + await page + .locator(`#${modalName}`) + .locator("details") + .getByLabel("Storage limit in MB:") + .first() + .fill("555"); + await page + .locator(`#${modalName}`) + .locator("details") + .getByRole("button", { name: "Update storage limit" }) + .click(); + await expect(page.getByText("Successfully updated user website storage size")).toBeVisible(); +}); + +test(`Delete user`, async ({ page }) => { + await register(userDeleted, page); + await authenticate(userOwner, page); + await page.getByRole("link", { name: "Manage" }).click(); + await page + .locator("tr") + .filter({ hasText: userDeleted }) + .getByRole("button", { name: "Manage" }) + .first() + .click(); + const modalName = page.url().split("#")[1]; + await page.locator(`#${modalName}`).locator('summary:has-text("Delete")').click(); + await page.locator(`#${modalName}`).getByRole("button", { name: "Delete user" }).click(); + await expect(page.getByText("Successfully deleted user")).toBeVisible(); +}); diff --git a/web-app/tests/publish.spec.ts b/web-app/tests/publish.spec.ts new file mode 100644 index 0000000..7ff4a82 --- /dev/null +++ b/web-app/tests/publish.spec.ts @@ -0,0 +1,135 @@ +import { test, expect } from "@playwright/test"; +import { + userOwner, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite +} from "./shared"; + +test.describe("Website owner", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Publish" }).click(); + }); + + test(`Publish website`, async ({ page }) => { + await page.getByRole("button", { name: "Publish" }).click(); + await expect(page.getByText("Successfully published website")).toBeVisible(); + await expect(page.getByText("Your website is published at")).toBeVisible(); + }); + + test(`Set custom domain prefix`, async ({ page }) => { + await page.getByLabel("Prefix:").click(); + await page.getByLabel("Prefix:").press("ControlOrMeta+a"); + await page.getByLabel("Prefix:").fill("example-prefix"); + await page.getByRole("button", { name: "Update domain prefix" }).click(); + await expect(page.getByText("Successfully created/updated domain prefix")).toBeVisible(); + }); + + test(`Delete custom domain prefix`, async ({ page }) => { + await page.getByLabel("Prefix:").click(); + await page.getByLabel("Prefix:").press("ControlOrMeta+a"); + await page.getByLabel("Prefix:").fill("example-prefix"); + await page.getByRole("button", { name: "Update domain prefix" }).click(); + + await page.getByRole("button", { name: "Delete" }).click(); + await page.getByRole("button", { name: "Delete domain prefix" }).click(); + await expect(page.getByText("Successfully deleted domain prefix")).toBeVisible(); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test(`Publish website`, async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Publish" }).click(); + + await page + .getByRole("button", { name: "Publish" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Publish" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully published website")).toBeVisible(); + await expect(page.getByText("Your website is published at")).toBeVisible(); + } + }); + + test(`Set custom domain prefix`, async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Publish" }).click(); + + await page.getByLabel("Prefix:").click(); + await page.getByLabel("Prefix:").press("ControlOrMeta+a"); + await page.getByLabel("Prefix:").fill("new-prefix"); + await page + .getByRole("button", { name: "Update domain prefix" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update domain prefix" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully created/updated domain prefix")).toBeVisible(); + } + }); + + test(`Delete custom domain prefix`, async ({ page, browserName }) => { + test.skip(browserName === "firefox", "Some issues with Firefox in headful mode"); + + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Publish" }).click(); + + await page.getByLabel("Prefix:").click(); + await page.getByLabel("Prefix:").press("ControlOrMeta+a"); + await page.getByLabel("Prefix:").fill("new-prefix"); + await page.getByRole("button", { name: "Update domain prefix" }).click(); + await page.waitForResponse(/createUpdateCustomDomainPrefix/); + await page.getByRole("link", { name: "Account" }).click(); + await page.getByRole("button", { name: "Logout" }).click(); + + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + await page.getByRole("link", { name: "Publish" }).click(); + + await page.getByRole("button", { name: "Delete" }).click(); + await page + .getByRole("button", { name: "Delete domain prefix" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Delete domain prefix" }).click(); + + if ([10, 20].includes(permissionLevel)) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully deleted domain prefix")).toBeVisible(); + } + }); + }); +} diff --git a/web-app/tests/settings.spec.ts b/web-app/tests/settings.spec.ts new file mode 100644 index 0000000..1c332f1 --- /dev/null +++ b/web-app/tests/settings.spec.ts @@ -0,0 +1,175 @@ +import { test, expect } from "@playwright/test"; +import { randomBytes } from "node:crypto"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { + userOwner, + authenticate, + permissionLevels, + collabUsers, + collabTestingWebsite +} from "./shared"; + +const genRandomHex = () => `#${randomBytes(3).toString("hex")}`; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +test.describe("Website owner", () => { + test.beforeEach(async ({ page }) => { + await authenticate(userOwner, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + }); + + test("Update global", async ({ page, browserName }) => { + test.skip(browserName === "firefox", "Some issues with Firefox in headless mode"); + + await page.getByLabel("Background color dark theme:").click(); + await page.getByLabel("Background color dark theme:").fill(genRandomHex()); + await page.getByLabel("Background color light theme:").click(); + await page.getByLabel("Background color light theme:").fill(genRandomHex()); + await page.getByLabel("Accent color dark theme:").click(); + await page.getByLabel("Accent color dark theme:").fill(genRandomHex()); + await page.getByLabel("Accent color light theme:").click(); + await page.getByLabel("Accent color light theme:").fill(genRandomHex()); + await page.getByLabel("Favicon:").click(); + await page + .getByLabel("Favicon:") + .setInputFiles(join(__dirname, "sample-files", "archtika-logo-512x512.png")); + await page.getByRole("button", { name: "Update global" }).click(); + await expect(page.getByText("Successfully updated global")).toBeVisible(); + }); + + test("Update header", async ({ page, browserName }) => { + test.skip(browserName === "firefox", "Some issues with Firefox in headless mode"); + + await page.getByLabel("Logo type:").selectOption("image"); + await page.getByLabel("Logo text:").click(); + await page.getByLabel("Logo text:").press("ControlOrMeta+a"); + await page.getByLabel("Logo text:").fill("Logo text"); + await page.getByLabel("Logo image:").click(); + await page + .getByLabel("Logo image") + .setInputFiles(join(__dirname, "sample-files", "archtika-logo-512x512.png")); + await page.getByRole("button", { name: "Update header" }).click(); + await expect(page.getByText("Successfully updated header")).toBeVisible(); + }); + + test("Update home", async ({ page }) => { + await page.getByLabel("Description:").click(); + await page.getByLabel("Description:").fill("Description comes here"); + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").press("ControlOrMeta+a"); + await page.getByLabel("Main content:").fill("## Updated main content"); + await page.getByRole("button", { name: "Update home" }).click(); + await expect(page.getByText("Successfully updated home")).toBeVisible(); + }); + + test("Update footer", async ({ page }) => { + await page.getByLabel("Additional text:").click(); + await page.getByLabel("Additional text:").press("ControlOrMeta+a"); + await page.getByLabel("Additional text:").fill("Updated footer content"); + await page.getByRole("button", { name: "Update footer" }).click(); + await expect(page.getByText("Successfully updated footer")).toBeVisible(); + }); +}); + +for (const permissionLevel of permissionLevels) { + test.describe(`Website collaborator (Permission level: ${permissionLevel})`, () => { + test.beforeEach(async ({ page }) => { + await authenticate(collabUsers.get(permissionLevel)!, page); + await page + .locator("li") + .filter({ hasText: collabTestingWebsite }) + .getByRole("link", { name: collabTestingWebsite }) + .click(); + }); + + test("Update global", async ({ page, browserName }) => { + test.skip(browserName === "firefox", "Some issues with Firefox in headless mode"); + + await page.getByLabel("Background color dark theme:").click(); + await page.getByLabel("Background color dark theme:").fill(genRandomHex()); + await page.getByLabel("Background color light theme:").click(); + await page.getByLabel("Background color light theme:").fill(genRandomHex()); + await page.getByLabel("Accent color dark theme:").click(); + await page.getByLabel("Accent color dark theme:").fill(genRandomHex()); + await page.getByLabel("Accent color light theme:").click(); + await page.getByLabel("Accent color light theme:").fill(genRandomHex()); + await page.getByLabel("Favicon:").click(); + await page + .getByLabel("Favicon:") + .setInputFiles(join(__dirname, "sample-files", "archtika-logo-512x512.png")); + await page + .getByRole("button", { name: "Update global" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update global" }).click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated global")).toBeVisible(); + } + }); + + test("Update header", async ({ page, browserName }) => { + test.skip(browserName === "firefox", "Some issues with Firefox in headless mode"); + + await page.getByLabel("Logo type:").selectOption("image"); + await page.getByLabel("Logo text:").click(); + await page.getByLabel("Logo text:").press("ControlOrMeta+a"); + await page.getByLabel("Logo text:").fill("Logo text"); + await page.getByLabel("Logo image:").click(); + await page + .getByLabel("Logo image") + .setInputFiles(join(__dirname, "sample-files", "archtika-logo-512x512.png")); + await page + .getByRole("button", { name: "Update header" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update header" }).click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated header")).toBeVisible(); + } + }); + + test("Update home", async ({ page }) => { + await page.getByLabel("Description:").click(); + await page.getByLabel("Description:").fill("Description comes here"); + await page.getByLabel("Main content:").click(); + await page.getByLabel("Main content:").press("ControlOrMeta+a"); + await page.getByLabel("Main content:").fill("## Updated main content"); + await page + .getByRole("button", { name: "Update home" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update home" }).click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated home")).toBeVisible(); + } + }); + + test("Update footer", async ({ page }) => { + await page.getByLabel("Additional text:").click(); + await page.getByLabel("Additional text:").press("ControlOrMeta+a"); + await page.getByLabel("Additional text:").fill("Updated footer content"); + await page + .getByRole("button", { name: "Update footer" }) + .evaluate((node) => node.removeAttribute("disabled")); + await page.getByRole("button", { name: "Update footer" }).click(); + + if (permissionLevel === 10) { + await expect(page.getByText("Insufficient permissions")).toBeVisible(); + } else { + await expect(page.getByText("Successfully updated footer")).toBeVisible(); + } + }); + }); +} diff --git a/web-app/tests/shared.ts b/web-app/tests/shared.ts new file mode 100644 index 0000000..47cbfce --- /dev/null +++ b/web-app/tests/shared.ts @@ -0,0 +1,35 @@ +import type { Page } from "@playwright/test"; + +export const userOwner = "test-owner"; +export const userCollab10 = "test-collab10"; +export const userCollab20 = "test-collab20"; +export const userCollab30 = "test-collab30"; +export const userDummy = "test-dummy"; + +export const collabUsers = new Map([ + [10, userCollab10], + [20, userCollab20], + [30, userCollab30] +]); +export const permissionLevels = [10, 20, 30]; +export const allUsers = [userOwner, userCollab10, userCollab20, userCollab30, userDummy]; +export const password = "T3stinguser?!"; +export const contentTypes = ["Blog", "Docs"]; +export const collabTestingWebsite = "Collaborator testing"; + +export const register = async (username: string, page: Page) => { + await page.goto("/register"); + await page.getByLabel("Username:").click(); + await page.getByLabel("Username:").fill(username); + await page.getByLabel("Password:").click(); + await page.getByLabel("Password:").fill(password); + await page.getByRole("button", { name: "Register" }).click(); +}; +export const authenticate = async (username: string, page: Page) => { + await page.goto("/login"); + await page.getByLabel("Username:").click(); + await page.getByLabel("Username:").fill(username); + await page.getByLabel("Password:").click(); + await page.getByLabel("Password:").fill(password); + await page.getByRole("button", { name: "Login" }).click(); +}; diff --git a/web-app/tests/website.spec.ts b/web-app/tests/website.spec.ts deleted file mode 100644 index 2dc2574..0000000 --- a/web-app/tests/website.spec.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { test as base, expect, type Page } from "@playwright/test"; -import { fileURLToPath } from "node:url"; -import { dirname, join } from "node:path"; -import { randomBytes } from "node:crypto"; -import { platform } from "node:os"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const username = randomBytes(8).toString("hex"); -const collabUsername = randomBytes(8).toString("hex"); -const customPrefix = Buffer.from(randomBytes(16).map((byte) => (byte % 26) + 97)).toString(); -const customPrefix2 = Buffer.from(randomBytes(16).map((byte) => (byte % 26) + 97)).toString(); -const password = "T3stuser??!!"; - -const test = base.extend<{ authenticatedPage: Page }>({ - authenticatedPage: async ({ page }, use) => { - await page.goto("/login"); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await use(page); - } -}); - -test.describe.serial("Website tests", () => { - test("Register", async ({ page }) => { - await page.goto("/register"); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(username); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - }); - - test("Create websites", async ({ authenticatedPage: page }) => { - await page.getByRole("button", { name: "Create website" }).click(); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Blog"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByRole("link", { name: "All websites" })).toBeVisible(); - await expect(page.getByText("Search & Filter")).toBeVisible(); - await expect(page.getByText("Blog Type: Blog Created at:")).toBeVisible(); - - await page.getByRole("button", { name: "Create website" }).click(); - await page.getByLabel("Type: BlogDocs").selectOption("Docs"); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Documentation"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByRole("link", { name: "All websites" })).toBeVisible(); - await expect(page.getByText("Search & Filter")).toBeVisible(); - await expect(page.getByText("Documentation Type: Docs")).toBeVisible(); - }); - - test("Update websites", async ({ authenticatedPage: page }) => { - await page.locator("li").filter({ hasText: "Blog" }).getByRole("button").first().click(); - await page.getByRole("textbox", { name: "Title" }).click(); - await page.getByRole("textbox", { name: "Title" }).fill("Blog updated"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByRole("link", { name: "Blog updated" })).toBeVisible(); - - await page - .locator("li") - .filter({ hasText: "Documentation" }) - .getByRole("button") - .first() - .click(); - await page.getByRole("textbox", { name: "Title" }).click(); - await page.getByRole("textbox", { name: "Title" }).fill("Documentation updated"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByRole("link", { name: "Documentation updated" })).toBeVisible(); - }); - - test.describe.serial("Blog", () => { - test.describe.serial("Update settings", () => { - test("Global", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByLabel("Background color dark theme: ").click(); - await page.getByLabel("Background color dark theme:").fill("#3975a2"); - await page.getByLabel("Background color light theme:").click(); - await page.getByLabel("Background color light theme:").fill("#41473e"); - await page.getByLabel("Accent color dark theme: ").click(); - await page.getByLabel("Accent color dark theme:").fill("#3975a2"); - await page.getByLabel("Accent color light theme:").click(); - await page.getByLabel("Accent color light theme:").fill("#41473e"); - await page.locator("#global").getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated global")).toBeVisible(); - await page.getByLabel("Favicon:").click(); - await page - .getByLabel("Favicon:") - .setInputFiles(join(__dirname, "sample-files", "archtika-logo-512x512.png")); - await page.locator("#global").getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated global")).toBeVisible(); - }); - - test("Header", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByLabel("Logo text:").click(); - await page.getByLabel("Logo text:").fill("archtika Blog updated"); - await page.locator("#header").getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated header")).toBeVisible(); - await page.getByLabel("Logo type: TextImage").selectOption("image"); - await page.getByLabel("Logo image:").click(); - await page - .getByLabel("Logo image:") - .setInputFiles(join(__dirname, "sample-files", "archtika-logo-512x512.png")); - await page.locator("#header").getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated header")).toBeVisible(); - }); - - test("Home", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByLabel("Description:").click(); - await page.getByLabel("Description:").fill("Description"); - await page.getByLabel("Main content:").click(); - await page.getByLabel("Main content:").press("Control+a"); - await page.getByLabel("Main content:").fill("## Some new content comes here"); - await expect(page.getByRole("link", { name: "Some new content comes here" })).toBeVisible(); - await page.locator("#home").getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated home")).toBeVisible(); - }); - - test("Footer", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByLabel("Additional text:").click(); - await page - .getByLabel("Additional text:") - .fill( - "archtika is a free, open, modern, performant and lightweight CMS updated content comes here" - ); - await page.locator("#footer").getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated footer")).toBeVisible(); - }); - }); - - test.describe.serial("Articles", () => { - test("Create article", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("button", { name: "Create article" }).click(); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Test article"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByRole("link", { name: "All articles" })).toBeVisible(); - await expect(page.getByText("Search & Filter")).toBeVisible(); - await expect(page.getByText("Test article Edit Delete")).toBeVisible(); - }); - - test("Update article", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("link", { name: "Edit" }).click(); - await page.getByLabel("Description:").click(); - await page.getByLabel("Description:").fill("Sample article description"); - await page.getByLabel("Author:").click(); - await page.getByLabel("Author:").fill("John Doe"); - await page.getByLabel("Main content:").click(); - await page - .getByLabel("Main content:") - .fill( - "## Section\n\n### Subsection\n\n## Second section\n\n### Second subsection\n\n#### Sub Sub section" - ); - await expect( - page.getByText( - "Table of contents SectionSubsectionSecond sectionSecond subsectionSub Sub" - ) - ).toBeVisible(); - await expect( - page.getByRole("heading", { name: "Section", exact: true }).getByRole("link") - ).toBeVisible(); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated article")).toBeVisible(); - }); - - test("Paste image", async ({ authenticatedPage: page, context }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("link", { name: "Edit" }).click(); - await page.getByLabel("Main content:").click(); - - await context.grantPermissions(["clipboard-read", "clipboard-write"]); - const isMac = platform() === "darwin"; - const modifier = isMac ? "Meta" : "Control"; - - const clipPage = await context.newPage(); - await clipPage.goto("https://picsum.photos/400/400.jpg"); - await clipPage.keyboard.press(`${modifier}+C`); - - await page.bringToFront(); - await page.keyboard.press("Enter"); - await page.keyboard.press("Enter"); - await page.keyboard.press(`${modifier}+V`); - - await expect(page.getByText("Successfully uploaded image")).toBeVisible(); - }); - - test("Delete article", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page.getByRole("button", { name: "Delete article" }).click(); - await expect(page.getByText("Successfully deleted article")).toBeVisible(); - }); - }); - - test.describe.serial("Collaborators", () => { - test("Add collaborator", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page.getByRole("button", { name: "Add collaborator" }).click(); - await page.getByLabel("Username:").click(); - await page.getByLabel("Username:").fill(collabUsername); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully added")).toBeVisible(); - }); - - test("Update collaborator", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page.getByRole("button", { name: "Update" }).click(); - await page.getByRole("combobox").selectOption("20"); - await page.getByRole("button", { name: "Update collaborator" }).click(); - await expect(page.getByText("Successfully updated")).toBeVisible(); - }); - - test("Remove collaborator", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Collaborators" }).click(); - await page.getByRole("button", { name: "Remove" }).click(); - await page.getByRole("button", { name: "Remove collaborator" }).click(); - await expect(page.getByText("Successfully removed")).toBeVisible(); - }); - }); - - test.describe.serial("Legal information", () => { - test("Create/Update legal information", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Legal information" }).click(); - await page.getByLabel("Main content:").click(); - await page.getByLabel("Main content:").fill("## Content"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully created/updated legal")).toBeVisible(); - - await page.getByLabel("Main content:").click(); - await page.getByLabel("Main content:").fill("## Content updated"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully created/updated legal")).toBeVisible(); - }); - test("Delete legal information", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Legal information" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page.getByRole("button", { name: "Delete legal information" }).click(); - await expect(page.getByText("Successfully deleted legal")).toBeVisible(); - }); - }); - }); - - test.describe.serial("Docs", () => { - test.describe.serial("Categories", () => { - test("Create category", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page.getByRole("button", { name: "Create category" }).click(); - await page.getByLabel("Name:").nth(0).click(); - await page.getByLabel("Name:").nth(0).fill("Category"); - await page.getByLabel("Weight:").click(); - await page.getByLabel("Weight:").fill("1000"); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully created category")).toBeVisible(); - await expect(page.getByRole("link", { name: "All categories" })).toBeVisible(); - await expect(page.getByText("Category (1000)")).toBeVisible(); - }); - test("Update category", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page.getByRole("button", { name: "Update" }).click(); - await page.getByRole("spinbutton", { name: "Weight:" }).click(); - await page.getByRole("spinbutton", { name: "Weight:" }).fill("500"); - await page.getByRole("button", { name: "Update category" }).click(); - await expect(page.getByText("Successfully updated category")).toBeVisible(); - await expect(page.getByText("Category (500)")).toBeVisible(); - }); - test("Delete category", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page.getByRole("button", { name: "Delete category" }).click(); - await expect(page.getByText("Successfully deleted category")).toBeVisible(); - await expect(page.getByRole("link", { name: "All categories" })).toBeHidden(); - }); - }); - - test("Article", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Categories" }).click(); - await page.getByRole("button", { name: "Create category" }).click(); - await page.getByLabel("Name:").nth(0).click(); - await page.getByLabel("Name:").nth(0).fill("Category"); - await page.getByLabel("Weight:").click(); - await page.getByLabel("Weight:").fill("1000"); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Articles" }).click(); - await page.getByRole("button", { name: "Create article" }).click(); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Article"); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Edit" }).click(); - await page.getByLabel("Weight:").click(); - await page.getByLabel("Weight:").fill("1000"); - await page.getByLabel("Title:").click(); - await page.getByLabel("Title:").fill("Article"); - await page.getByLabel("Description:").click(); - await page.getByLabel("Description:").fill("Testing out this article"); - await page.getByLabel("Author:").click(); - await page.getByLabel("Author:").fill("John Doe"); - await page.getByLabel("Main content:").click(); - await page - .getByLabel("Main content:") - .fill( - "## Main content comes in here\n\n### First section\n\n### Second section\n\n## More" - ); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully updated article")).toBeVisible(); - await expect(page.getByText("Table of contents Main")).toBeVisible(); - await expect( - page.getByRole("heading", { name: "Main content comes in here" }).getByRole("link") - ).toBeVisible(); - }); - }); - - test("Publish websites", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByRole("button", { name: "Publish" }).click(); - await expect(page.getByText("Successfully published website")).toBeVisible(); - await expect(page.getByText("Your website is published at")).toBeVisible(); - - await page.goto("/"); - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByRole("button", { name: "Publish" }).click(); - await expect(page.getByText("Successfully published website")).toBeVisible(); - await expect(page.getByText("Your website is published at")).toBeVisible(); - }); - - test("Set custom domain prefixes", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByLabel("Prefix:").click(); - await page.getByLabel("Prefix:").fill(customPrefix); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully created/updated")).toBeVisible(); - - await page.goto("/"); - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByLabel("Prefix:").click(); - await page.getByLabel("Prefix:").fill(customPrefix2); - await page.getByRole("button", { name: "Submit" }).click(); - await expect(page.getByText("Successfully created/updated")).toBeVisible(); - }); - - test("Remove custom domain prefixes", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Blog" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page.getByRole("button", { name: "Delete domain prefix" }).click(); - await expect(page.getByText("Successfully deleted domain")).toBeVisible(); - - await page.goto("/"); - await page.getByRole("link", { name: "Documentation" }).click(); - await page.getByRole("link", { name: "Publish" }).click(); - await page.getByRole("button", { name: "Delete" }).click(); - await page.getByRole("button", { name: "Delete domain prefix" }).click(); - await expect(page.getByText("Successfully deleted domain")).toBeVisible(); - }); - - test("Delete websites", async ({ authenticatedPage: page }) => { - await page.getByRole("button", { name: "Delete" }).nth(1).click(); - await page.getByRole("button", { name: "Delete website" }).click(); - await expect(page.getByText("Successfully deleted website")).toBeVisible(); - - await page.getByRole("button", { name: "Delete" }).click(); - await page.getByRole("button", { name: "Delete website" }).click(); - await expect(page.getByText("Successfully deleted website")).toBeVisible(); - - await expect(page.getByRole("link", { name: "All websites" })).toBeHidden(); - }); - - test("Delete accounts", async ({ authenticatedPage: page }) => { - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - - await page.getByLabel("Username:").fill(collabUsername); - await page.getByLabel("Password:").fill(password); - await page.getByRole("button", { name: "Submit" }).click(); - await page.getByRole("link", { name: "Account" }).click(); - await page.getByRole("button", { name: "Delete account" }).click(); - await page.getByLabel("Password:").click(); - await page.getByLabel("Password:").fill(password); - await page - .locator("#delete-account-modal") - .getByRole("button", { name: "Delete account" }) - .click(); - }); -});