Skip to content

Commit

Permalink
#8581: More page editor poms (#8748)
Browse files Browse the repository at this point in the history
* brick action panel

* adds more page editor poms

* fix type import
  • Loading branch information
fungairino authored Jul 3, 2024
1 parent 7f0595e commit 3ab86b4
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 59 deletions.
36 changes: 36 additions & 0 deletions end-to-end-tests/pageObjects/pageEditor/brickActionsPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { BasePageObject } from "../basePageObject";
import { ModifiesModState } from "./utils";

export class BrickActionsPanel extends BasePageObject {
getAddBrickButton(n: number) {
return this.getByTestId(/icon-button-.*-add-brick/).nth(n);
}

@ModifiesModState
async addBrick(brickName: string, { index = 0 }: { index?: number } = {}) {
await this.getAddBrickButton(index).click();

// Add brick modal
await this.page.getByTestId("tag-search-input").fill(brickName);
await this.page.getByRole("button", { name: brickName }).first().click();

await this.page.getByRole("button", { name: "Add brick" }).click();
}
}
31 changes: 31 additions & 0 deletions end-to-end-tests/pageObjects/pageEditor/brickConfigurationPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { BasePageObject } from "../basePageObject";
import { ModifiesModState } from "./utils";

export class BrickConfigurationPanel extends BasePageObject {
@ModifiesModState
async fillField(fieldLabel: string, value: string) {
await this.getByLabel(fieldLabel).fill(value);
}

@ModifiesModState
async fillFieldByPlaceholder(fieldPlaceholder: string, value: string) {
await this.getByPlaceholder(fieldPlaceholder).fill(value);
}
}
20 changes: 20 additions & 0 deletions end-to-end-tests/pageObjects/pageEditor/dataPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { BasePageObject } from "../basePageObject";

export class DataPanel extends BasePageObject {}
25 changes: 25 additions & 0 deletions end-to-end-tests/pageObjects/pageEditor/modEditorPane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { BasePageObject } from "../basePageObject";

export class ModEditorPane extends BasePageObject {
modId = this.getByLabel("Mod ID");
name = this.getByLabel("Name");
version = this.getByLabel("Version");
description = this.getByLabel("Description");
}
66 changes: 24 additions & 42 deletions end-to-end-tests/pageObjects/pageEditor/pageEditorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,38 @@
*/

import { getBasePageEditorUrl } from "../constants";
import { type Page, expect } from "@playwright/test";
import { type Page, expect, type Locator } from "@playwright/test";
import { ModsPage } from "../extensionConsole/modsPage";
import { WorkshopPage } from "../extensionConsole/workshop/workshopPage";
import { type UUID } from "@/types/stringTypes";
import { BasePageObject } from "../basePageObject";
import { ModListingPanel } from "./modListingPanel";
import { BrickActionsPanel } from "./brickActionsPanel";
import { BrickConfigurationPanel } from "./brickConfigurationPanel";
import { DataPanel } from "./dataPanel";
import { ModEditorPane } from "./modEditorPane";
import { ModifiesModState } from "./utils";

/**
* Page object for the Page Editor. Prefer the newPageEditorPage fixture in testBase.ts to directly creating an
* instance of this class to take advantage of automatic cleanup of saved mods.
*
* @knip usage of PageEditorPage indirectly via the newPageEditorPage fixture in testBase.ts causes a
* false-positive
*/
export class PageEditorPage extends BasePageObject {
private readonly pageEditorUrl: string;
private readonly savedStandaloneModNames: string[] = [];
private readonly savedPackageModIds: string[] = [];

modListingPanel = new ModListingPanel(this.getByTestId("modListingPanel"));
brickActionsPanel = new BrickActionsPanel(
this.getByTestId("brickActionsPanel"),
);

modEditorPane = new ModEditorPane(this.getByTestId("modEditorPane"));
brickConfigurationPanel = new BrickConfigurationPanel(
this.getByTestId("brickConfigurationPanel"),
);

dataPanel = new DataPanel(this.getByTestId("dataPanel"));

templateGalleryButton = this.getByRole("button", {
name: "Launch Template Gallery",
Expand Down Expand Up @@ -65,37 +77,13 @@ export class PageEditorPage extends BasePageObject {
await this.page.bringToFront();
}

async waitForReduxUpdate() {
// See EditorPane.tsx:REDUX_SYNC_WAIT_MILLIS
// eslint-disable-next-line playwright/no-wait-for-timeout -- Wait for Redux to update
await this.page.waitForTimeout(500);
}

async setStarterBrickName(modComponentName: string) {
await this.fillInBrickField("Name", modComponentName);
await this.waitForReduxUpdate();
}

async fillInBrickField(fieldLabel: string, value: string) {
await this.getByLabel(fieldLabel).fill(value);
await this.waitForReduxUpdate();
}

async addBrickToModComponent(
brickName: string,
{ index = 0 }: { index?: number } = {},
/** Used for interactions that require selecting an element on the connected page, such as the button starter brick */
@ModifiesModState
async selectConnectedPageElement(
connectedPage: Page,
selectLocator: Locator,
expectedElementSelector: string,
) {
await this.getByTestId(/icon-button-.*-add-brick/)
.nth(index)
.click();

await this.getByTestId("tag-search-input").fill(brickName);
await this.getByRole("button", { name: brickName }).click();

await this.getByRole("button", { name: "Add brick" }).click();
}

async selectConnectedPageElement(connectedPage: Page) {
// Without focusing first, the click doesn't enable selection tool ¯\_(ツ)_/¯
await this.getByLabel("Select element").focus();
await this.getByLabel("Select element").click();
Expand All @@ -104,16 +92,12 @@ export class PageEditorPage extends BasePageObject {
await expect(
connectedPage.getByText("Selection Tool: 0 matching"),
).toBeVisible();
await connectedPage
.getByRole("heading", { name: "Transaction Table" })
.click();
await selectLocator.click();

await this.page.bringToFront();
await expect(this.getByPlaceholder("Select an element")).toHaveValue(
"#root h1",
expectedElementSelector,
);

await this.waitForReduxUpdate();
}

/**
Expand All @@ -138,8 +122,6 @@ export class PageEditorPage extends BasePageObject {
}

async saveStandaloneMod(modName: string) {
// Wait for redux to persist the page editor mod changes before saving.
await this.waitForReduxUpdate();
const modListItem = this.modListingPanel.getModListItemByName(modName);
await modListItem.activate();
await modListItem.saveButton.click();
Expand Down
35 changes: 35 additions & 0 deletions end-to-end-tests/pageObjects/pageEditor/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type BasePageObject } from "../basePageObject";

type AsyncFunction<T> = (...args: any[]) => Promise<T>;

// Decorator used for functions that modify the state of the mod.
// This is used to wait for Redux to update before continuing.
export function ModifiesModState<T>(
value: AsyncFunction<T>,
context: ClassMethodDecoratorContext<BasePageObject, AsyncFunction<T>>,
) {
return async function (this: BasePageObject, ...args: any[]): Promise<T> {
const result = await value.apply(this, args);
// See EditorPane.tsx:REDUX_SYNC_WAIT_MILLIS
// eslint-disable-next-line playwright/no-wait-for-timeout -- Wait for Redux to update
await this.page.waitForTimeout(500);
return result;
};
}
36 changes: 23 additions & 13 deletions end-to-end-tests/tests/modLifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,43 @@ test("create, run, package, and update mod", async ({
await page.getByRole("button", { name: "Action #3" }).click();

await pageEditorPage.bringToFront();
await pageEditorPage.getByLabel("Button text").fill("Search Youtube");
await pageEditorPage.setStarterBrickName(modComponentName);
await pageEditorPage.brickConfigurationPanel.fillField(
"Button text",
"Search Youtube",
);
await pageEditorPage.brickConfigurationPanel.fillField(
"name",
modComponentName,
);
});

await test.step("Add the Extract from Page brick and configure it", async () => {
await pageEditorPage.addBrickToModComponent("extract from page");
await pageEditorPage.brickActionsPanel.addBrick("extract from page");

await pageEditorPage.getByPlaceholder("Property name").fill("searchText");
await expect(pageEditorPage.getByPlaceholder("Property name")).toHaveValue(
await pageEditorPage.brickConfigurationPanel.fillFieldByPlaceholder(
"Property name",
"searchText",
);

await pageEditorPage.selectConnectedPageElement(page);
await pageEditorPage.selectConnectedPageElement(
page,
page.getByRole("heading", { name: "Transaction Table" }),
"#root h1",
);
});

await test.step("Add the YouTube search brick and configure it", async () => {
await pageEditorPage.addBrickToModComponent("YouTube search in new tab", {
index: 1,
});
await pageEditorPage.brickActionsPanel.addBrick(
"YouTube search in new tab",
{
index: 1,
},
);

await pageEditorPage.getByLabel("Query").click();
await pageEditorPage.fillInBrickField(
await pageEditorPage.brickConfigurationPanel.fillField(
"Query",
"{{ @data.searchText }} + Foo",
);

await pageEditorPage.waitForReduxUpdate();
});

const { modId } = await pageEditorPage.createModFromModComponent({
Expand Down
7 changes: 5 additions & 2 deletions end-to-end-tests/tests/pageEditor/saveMod.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ test("can save a standalone trigger mod", async ({
const pageEditorPage = await newPageEditorPage(page.url());
const { modComponentName } =
await pageEditorPage.modListingPanel.addStarterBrick("Trigger");
await pageEditorPage.setStarterBrickName(modComponentName);
await pageEditorPage.brickConfigurationPanel.fillField(
"name",
modComponentName,
);
await pageEditorPage.saveStandaloneMod(modComponentName);
const modsPage = new ModsPage(page, extensionId);
await modsPage.goto();
Expand All @@ -56,7 +59,7 @@ test("shows error notification when updating a public mod without incrementing t
const modListItem =
pageEditorPage.modListingPanel.getModListItemByName(modName);
await modListItem.activate();
await pageEditorPage.fillInBrickField("Name", "8203 Repro Updated");
await pageEditorPage.modEditorPane.name.fill("8203 Repro Updated");
await pageEditorPage.saveSelectedPackagedMod();
await expect(pageEditorPage.getIncrementVersionErrorToast()).toBeVisible();
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@ test("#8104: Do not automatically close the sidebar when saving in the Page Edit

const { modComponentName } =
await pageEditorPage.modListingPanel.addStarterBrick("Sidebar Panel");
await pageEditorPage.setStarterBrickName(modComponentName);
await pageEditorPage.brickConfigurationPanel.fillField(
"name",
modComponentName,
);

const sidebar = await getSidebarPage(page, extensionId);
await expect(
sidebar.getByRole("tab", { name: "Sidebar Panel" }),
).toBeVisible();

const updatedTabTitle = "Updated Tab Title";
await pageEditorPage.fillInBrickField("Tab Title", updatedTabTitle);
await pageEditorPage.brickConfigurationPanel.fillField(
"Tab Title",
updatedTabTitle,
);
await pageEditorPage.getRenderPanelButton().click();
await expect(
sidebar.getByRole("tab", { name: updatedTabTitle }),
Expand Down

0 comments on commit 3ab86b4

Please sign in to comment.