0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-08 16:18:11 -05:00

Merge pull request #4563 from penpot/ladybenko-workspace-draw-test

Refactor POMS + extra workspace tests
This commit is contained in:
Eva Marco 2024-05-09 16:19:46 +02:00 committed by GitHub
commit 99371234dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 174 additions and 90 deletions

View file

@ -0,0 +1,9 @@
[
{
"~:id": "~u088df3d4-d383-80f6-8004-527e50ea4f1f",
"~:revn": 21,
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:session-id": "~u1dc6d4fa-7bd3-803a-8004-527dd9df2c62",
"~:changes": []
}
]

View file

@ -1,10 +0,0 @@
import { test as base } from '@playwright/test'
export const test = base.extend({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page)
await use(loginPage)
}
})
export { expect } from '@playwright/test'

View file

@ -6,6 +6,7 @@ export class BasePage {
if (typeof path !== "string" && !(path instanceof RegExp)) {
throw new TypeError("Invalid path argument. Must be a string or a RegExp.");
}
const url = typeof path === "string" ? `**/api/rpc/command/${path}` : path;
const interceptConfig = {
status: 200,

View file

@ -8,7 +8,7 @@ export class BaseWebSocketPage extends BasePage {
* @param {Page} page
* @returns
*/
static setupWebSockets(page) {
static initWebSockets(page) {
return MockWebSocketHelper.init(page);
}

View file

@ -1,8 +1,8 @@
import { BasePage } from "./BasePage";
export class LoginPage extends BasePage {
static setupLoggedOutUser(page) {
return this.mockRPC(page, "get-profile", "get-profile-anonymous.json");
static async initWithLoggedOutUser(page) {
await BasePage.mockRPC(page, "get-profile", "get-profile-anonymous.json");
}
constructor(page) {
@ -10,8 +10,8 @@ export class LoginPage extends BasePage {
this.loginButton = page.getByRole("button", { name: "Login" });
this.password = page.getByLabel("Password");
this.userName = page.getByLabel("Email");
this.message = page.getByText("Email or password is incorrect");
this.badLoginMsg = page.getByText("Enter a valid email please");
this.invalidCredentialsError = page.getByText("Email or password is incorrect");
this.invalidEmailError = page.getByText("Enter a valid email please");
this.initialHeading = page.getByRole("heading", { name: "Log into my account" });
}
@ -24,7 +24,7 @@ export class LoginPage extends BasePage {
await this.loginButton.click();
}
async setupAllowedUser() {
async setupLoggedInUser() {
await this.mockRPC("get-profile", "logged-in-user/get-profile-logged-in.json");
await this.mockRPC("get-teams", "logged-in-user/get-teams-default.json");
await this.mockRPC("get-font-variants?team-id=*", "logged-in-user/get-font-variants-empty.json");

View file

@ -0,0 +1,99 @@
import { expect } from "@playwright/test";
import { BaseWebSocketPage } from "./BaseWebSocketPage";
export class WorkspacePage extends BaseWebSocketPage {
/**
* This should be called on `test.beforeEach`.
*
* @param {Page} page
* @returns
*/
static async init(page) {
await BaseWebSocketPage.initWebSockets(page);
await BaseWebSocketPage.mockRPC(page, "get-profile", "logged-in-user/get-profile-logged-in.json");
await BaseWebSocketPage.mockRPC(
page,
"get-team-users?file-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await BaseWebSocketPage.mockRPC(
page,
"get-comment-threads?file-id=*",
"workspace/get-comment-threads-empty.json",
);
await BaseWebSocketPage.mockRPC(page, "get-project?id=*", "workspace/get-project-default.json");
await BaseWebSocketPage.mockRPC(page, "get-team?id=*", "workspace/get-team-default.json");
await BaseWebSocketPage.mockRPC(
page,
"get-profiles-for-file-comments?file-id=*",
"workspace/get-profile-for-file-comments.json",
);
}
static anyProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
static anyFileId = "c7ce0794-0992-8105-8004-38f280443849";
static anyPageId = "c7ce0794-0992-8105-8004-38f28044384a";
#ws = null;
constructor(page) {
super(page);
this.pageName = page.getByTestId("page-name");
this.presentUserListItems = page.getByTestId("active-users-list").getByAltText("Princesa Leia");
this.viewport = page.getByTestId("viewport");
this.rootShape = page.locator(`[id="shape-00000000-0000-0000-0000-000000000000"]`);
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
}
async goToWorkspace() {
await this.page.goto(
`/#/workspace/${WorkspacePage.anyProjectId}/${WorkspacePage.anyFileId}?page-id=${WorkspacePage.anyPageId}`,
);
this.#ws = await this.waitForNotificationsWebSocket();
await this.#ws.mockOpen();
await this.#waitForWebSocketReadiness();
}
async #waitForWebSocketReadiness() {
// TODO: find a better event to settle whether the app is ready to receive notifications via ws
await expect(this.pageName).toHaveText("Page 1");
}
async sendPresenceMessage(fixture) {
await this.#ws.mockMessage(JSON.stringify(fixture));
}
async cleanUp() {
await this.#ws.mockClose();
}
async setupEmptyFile() {
await this.mockRPC("get-profile", "logged-in-user/get-profile-logged-in.json");
await this.mockRPC("get-team-users?file-id=*", "logged-in-user/get-team-users-single-user.json");
await this.mockRPC("get-comment-threads?file-id=*", "workspace/get-comment-threads-empty.json");
await this.mockRPC("get-project?id=*", "workspace/get-project-default.json");
await this.mockRPC("get-team?id=*", "workspace/get-team-default.json");
await this.mockRPC(
"get-profiles-for-file-comments?file-id=*",
"workspace/get-profile-for-file-comments.json",
);
await this.mockRPC(/get\-file\?/, "workspace/get-file-blank.json");
await this.mockRPC(
"get-file-object-thumbnails?file-id=*",
"workspace/get-file-object-thumbnails-blank.json",
);
await this.mockRPC("get-font-variants?team-id=*", "workspace/get-font-variants-empty.json");
await this.mockRPC("get-file-fragment?file-id=*", "workspace/get-file-fragment-blank.json");
await this.mockRPC("get-file-libraries?file-id=*", "workspace/get-file-libraries-empty.json");
}
async clickWithDragViewportAt(x, y, width, height) {
await this.page.waitForTimeout(100);
await this.viewport.hover({ position: { x, y } });
await this.page.mouse.down();
await this.viewport.hover({ position: { x: x + width, y: y + height } });
await this.page.mouse.up();
}
}

View file

@ -2,50 +2,49 @@ import { test, expect } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
test.beforeEach(async ({ page }) => {
await LoginPage.setupLoggedOutUser(page);
await LoginPage.initWithLoggedOutUser(page);
await page.goto("/#/auth/login");
});
test("Shows login page when going to index and user is logged out", async ({ page }) => {
test("User is redirected to the login page when logged out", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupAllowedUser();
await loginPage.setupLoggedInUser();
await expect(loginPage.page).toHaveURL(/auth\/login$/);
await expect(loginPage.initialHeading).toBeVisible();
});
test("User submit a wrong formated email ", async ({ page }) => {
const loginPage = new LoginPage(page);
test.describe("Login form", () => {
test("User logs in by filling the login form", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginSuccess();
await loginPage.setupLoggedInUser();
await loginPage.setupLoginSuccess();
await loginPage.fillEmailAndPasswordInputs("foo@example.com", "loremipsum");
await loginPage.clickLoginButton();
await loginPage.fillEmailAndPasswordInputs("foo", "lorenIpsum");
await page.waitForURL("**/dashboard/**");
await expect(loginPage.page).toHaveURL(/dashboard/);
});
await expect(loginPage.badLoginMsg).toBeVisible();
});
test("User logs in by filling the login form", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginSuccess();
await loginPage.setupAllowedUser();
await loginPage.fillEmailAndPasswordInputs("foo@example.com", "loremipsum");
await loginPage.clickLoginButton();
await page.waitForURL('**/dashboard/**');
await expect(loginPage.page).toHaveURL(/dashboard/);
});
test("User submits wrong credentials", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginError();
await loginPage.fillEmailAndPasswordInputs("test@example.com", "loremipsum");
await loginPage.clickLoginButton();
await expect(loginPage.message).toBeVisible();
await expect(loginPage.page).toHaveURL(/auth\/login$/);
test("User gets error message when submitting an bad formatted email ", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginSuccess();
await loginPage.fillEmailAndPasswordInputs("foo", "lorenIpsum");
await expect(loginPage.invalidEmailError).toBeVisible();
});
test("User gets error message when submitting wrong credentials", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginError();
await loginPage.fillEmailAndPasswordInputs("test@example.com", "loremipsum");
await loginPage.clickLoginButton();
await expect(loginPage.invalidCredentialsError).toBeVisible();
await expect(loginPage.page).toHaveURL(/auth\/login$/);
});
});

View file

@ -1,54 +1,40 @@
import { test, expect } from "@playwright/test";
import { BasePage } from "../pages/BasePage";
import { MockWebSocketHelper } from "../../helpers/MockWebSocketHelper";
import { WorkspacePage } from "../pages/WorkspacePage";
import { presenceFixture } from "../../data/workspace/ws-notifications";
const anyProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
const anyFileId = "c7ce0794-0992-8105-8004-38f280443849";
const anyPageId = "c7ce0794-0992-8105-8004-38f28044384a";
const setupWorkspaceUser = (page) => {
BasePage.mockRPC(page, "get-profile", "logged-in-user/get-profile-logged-in.json");
BasePage.mockRPC(page, "get-team-users?file-id=*", "logged-in-user/get-team-users-single-user.json");
BasePage.mockRPC(page, "get-comment-threads?file-id=*", "workspace/get-comment-threads-empty.json");
BasePage.mockRPC(page, "get-project?id=*", "workspace/get-project-default.json");
BasePage.mockRPC(page, "get-team?id=*", "workspace/get-team-default.json");
BasePage.mockRPC(page, /get\-file\?/, "workspace/get-file-blank.json");
BasePage.mockRPC(
page,
"get-file-object-thumbnails?file-id=*",
"workspace/get-file-object-thumbnails-blank.json",
);
BasePage.mockRPC(
page,
"get-profiles-for-file-comments?file-id=*",
"workspace/get-profile-for-file-comments.json",
);
BasePage.mockRPC(page, "get-font-variants?team-id=*", "workspace/get-font-variants-empty.json");
BasePage.mockRPC(page, "get-file-fragment?file-id=*", "workspace/get-file-fragment-blank.json");
BasePage.mockRPC(page, "get-file-libraries?file-id=*", "workspace/get-file-libraries-empty.json");
};
test.beforeEach(async ({ page }) => {
await MockWebSocketHelper.init(page);
await WorkspacePage.init(page);
});
test("User loads worskpace with empty file", async ({ page }) => {
await setupWorkspaceUser(page);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await page.goto(`/#/workspace/${anyProjectId}/${anyFileId}?page-id=${anyPageId}`);
await workspacePage.goToWorkspace();
await expect(page.getByTestId("page-name")).toHaveText("Page 1");
await expect(workspacePage.pageName).toHaveText("Page 1");
});
test("User receives notifications updates in the workspace", async ({ page }) => {
await setupWorkspaceUser(page);
await page.goto(`/#/workspace/${anyProjectId}/${anyFileId}?page-id=${anyPageId}`);
test("User receives presence notifications updates in the workspace", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.goToWorkspace();
await workspacePage.sendPresenceMessage(presenceFixture);
const ws = await MockWebSocketHelper.waitForURL("ws://0.0.0.0:3500/ws/notifications")
await ws.mockOpen();
await expect(page.getByTestId("page-name")).toHaveText("Page 1");
await ws.mockMessage(JSON.stringify(presenceFixture));
await expect(page.getByTestId("active-users-list").getByAltText("Princesa Leia")).toHaveCount(2);
await ws.mockClose();
});
test("User draws a rect", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-create-rect.json");
await workspacePage.goToWorkspace();
await workspacePage.rectShapeButton.click();
await workspacePage.clickWithDragViewportAt(128, 128, 200, 100);
const shape = await workspacePage.rootShape.locator("rect");
expect(shape).toHaveAttribute("width", "200");
expect(shape).toHaveAttribute("height", "100");
});

View file

@ -276,7 +276,7 @@
(hooks/setup-shortcuts node-editing? drawing-path? text-editing? grid-editing?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div.viewport {:style #js {"--zoom" zoom}}
[:div.viewport {:style #js {"--zoom" zoom} :data-testid "viewport"}
[:& top-bar/top-bar {:layout layout}]
[:div.viewport-overlays
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap