Skip to content

Commit

Permalink
Merge branch 'main' of github.com:pixiebrix/pixiebrix-extension into …
Browse files Browse the repository at this point in the history
…migrate-mod-component-form-state
  • Loading branch information
grahamlangford committed Jul 1, 2024
2 parents 2597cb1 + 45b1a8a commit 9d86d12
Show file tree
Hide file tree
Showing 16 changed files with 166 additions and 215 deletions.
9 changes: 5 additions & 4 deletions end-to-end-tests/pageObjects/basePageObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import { type Locator, type Page } from "playwright";
*
* @example
* class LoginPage extends BasePageObject {
* const usernameInput = this.getByPlaceholder('Username');
* const passwordInput = this.getByPlaceholder('Password');
* // define common locators at the top.
* usernameInput = this.getByPlaceholder('Username');
* passwordInput = this.getByPlaceholder('Password');
*
* async login(username, password) {
* await usernameInput.fill(username);
Expand Down Expand Up @@ -56,7 +57,7 @@ import { type Locator, type Page } from "playwright";
* const username = await dashboardPage.userProfile.getUsername();
*
* @param {Locator|Page} rootLocatorOrPage The root locator scoping this page object.
* If a Page is provided, the root locator will be the body.
* If a Page is provided, the root locator will be `locator("html")`.
*/
export class BasePageObject {
readonly root: Locator;
Expand All @@ -76,7 +77,7 @@ export class BasePageObject {
this.root = rootLocatorOrPage;
this.page = rootLocatorOrPage.page();
} else {
this.root = rootLocatorOrPage.locator("body");
this.root = rootLocatorOrPage.locator("html");
this.page = rootLocatorOrPage;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@

import { expect, type Page } from "@playwright/test";
import { getBaseExtensionConsoleUrl } from "../constants";
import { BasePageObject } from "../basePageObject";

export class LocalIntegrationsPage {
export class LocalIntegrationsPage extends BasePageObject {
private readonly extensionConsoleUrl?: string;

constructor(
private readonly page: Page,
extensionId?: string,
) {
constructor(page: Page, extensionId?: string) {
super(page);
this.extensionConsoleUrl =
extensionId && getBaseExtensionConsoleUrl(extensionId);
}
Expand All @@ -34,30 +33,26 @@ export class LocalIntegrationsPage {
await this.page.goto(this.extensionConsoleUrl);
}

await this.page
.getByRole("link", {
name: "Local Integrations",
})
.click();
await this.getByRole("link", {
name: "Local Integrations",
}).click();

await expect(
this.page.getByRole("heading", { name: "Local Integrations" }),
this.getByRole("heading", { name: "Local Integrations" }),
).toBeVisible();

await expect(this.page.getByTestId("loader")).toBeHidden();
await expect(this.getByTestId("loader")).toBeHidden();
}

async createNewIntegration(integrationName: string) {
await this.page
.getByRole("button", { name: "Add Local Integration" })
.click();
await this.getByRole("button", { name: "Add Local Integration" }).click();

await this.page
.getByPlaceholder("Start typing to find results")
.fill(integrationName);
await this.getByPlaceholder("Start typing to find results").fill(
integrationName,
);

await this.page.getByText(integrationName).first().click();
await this.getByText(integrationName).first().click();

await this.page.getByTestId(`${integrationName} detail button`).click();
await this.getByTestId(`${integrationName} detail button`).click();
}
}
97 changes: 42 additions & 55 deletions end-to-end-tests/pageObjects/extensionConsole/modsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@
import { expect, type Page } from "@playwright/test";
import { getBaseExtensionConsoleUrl } from "../constants";
import { ensureVisibility } from "../../utils";
import { BasePageObject } from "../basePageObject";

export class ModsPage {
export class ModsPage extends BasePageObject {
private readonly extensionConsoleUrl: string;

constructor(
private readonly page: Page,
extensionId: string,
) {
modTableItems = this.getByRole("table").locator(".list-group-item");
searchModsInput = this.getByTestId("blueprints-search-input");

constructor(page: Page, extensionId: string) {
super(page);
this.extensionConsoleUrl = getBaseExtensionConsoleUrl(extensionId);
}

Expand All @@ -40,39 +42,30 @@ export class ModsPage {
request.url().includes("/api/registry/bricks/"),
);
await this.page.goto(this.extensionConsoleUrl);
await expect(this.page.getByText("Extension Console")).toBeVisible();
await expect(this.getByText("Extension Console")).toBeVisible();
await registryPromise;

// Check that the page is stable, and that the content has finished loading
const activeModsHeading = this.page.getByRole("heading", {
const activeModsHeading = this.getByRole("heading", {
name: "Active Mods",
});
await ensureVisibility(activeModsHeading, { timeout: 10_000 });
const modTableItems = this.modTableItems();
const contentLoadedLocator = this.page
.getByText("Welcome to PixieBrix!")
.or(modTableItems.nth(0));
const contentLoadedLocator = this.getByText("Welcome to PixieBrix!").or(
this.modTableItems.nth(0),
);
await expect(contentLoadedLocator).toBeVisible();
}

async viewAllMods() {
await this.page.getByTestId("all-mods-mod-tab").click();
await this.getByTestId("all-mods-mod-tab").click();
}

async viewActiveMods() {
await this.page.getByTestId("active-mod-tab").click();
}

modTableItems() {
return this.page.getByRole("table").locator(".list-group-item");
await this.getByTestId("active-mod-tab").click();
}

modTableItemById(modId: string) {
return this.modTableItems().filter({ hasText: modId });
}

searchModsInput() {
return this.page.getByTestId("blueprints-search-input");
return this.modTableItems.filter({ hasText: modId });
}

/**
Expand All @@ -83,10 +76,10 @@ export class ModsPage {
*/
async actionForModByName(modName: string, actionName: string): Promise<void> {
await this.page.bringToFront();
await this.searchModsInput().fill(modName);
await expect(this.page.getByText(`results for "${modName}`)).toBeVisible();
await this.searchModsInput.fill(modName);
await expect(this.getByText(`results for "${modName}`)).toBeVisible();

const modSearchResult = this.page.locator(".list-group-item", {
const modSearchResult = this.locator(".list-group-item", {
hasText: modName,
});
await expect(modSearchResult).toBeVisible();
Expand All @@ -95,7 +88,7 @@ export class ModsPage {
await modSearchResult.locator(".dropdown").click();

// Click the delete button in the delete confirmation modal
await this.page.getByRole("button", { name: actionName }).click();
await this.getByRole("button", { name: actionName }).click();
}

/**
Expand All @@ -106,9 +99,9 @@ export class ModsPage {
*/
async deleteModByName(modName: string) {
await this.page.bringToFront();
await this.searchModsInput().fill(modName);
await expect(this.page.getByText(`results for "${modName}`)).toBeVisible();
const modToDelete = this.page.locator(".list-group-item", {
await this.searchModsInput.fill(modName);
await expect(this.getByText(`results for "${modName}`)).toBeVisible();
const modToDelete = this.locator(".list-group-item", {
hasText: modName,
});
await expect(modToDelete).toBeVisible();
Expand All @@ -124,9 +117,7 @@ export class ModsPage {
await deactivateOption.click({
timeout: 3000,
});
await expect(
this.page.getByText(`Deactivated mod: ${modName}`),
).toBeVisible();
await expect(this.getByText(`Deactivated mod: ${modName}`)).toBeVisible();
// Re-open the dropdown action menu to stay in the same state
await modToDelete.locator(".dropdown").click();
}
Expand All @@ -137,23 +128,33 @@ export class ModsPage {
});

// Click the delete button in the delete confirmation modal
await this.page.getByRole("button", { name: "Delete" }).click();
await this.getByRole("button", { name: "Delete" }).click();
await expect(
// Exact text varies by standalone mod vs. mod package
this.page.getByText(`Deleted mod ${modName}`),
this.getByText(`Deleted mod ${modName}`),
).toBeVisible();
}
}

export class ActivateModPage {
export class ActivateModPage extends BasePageObject {
private readonly baseConsoleUrl: string;
private readonly activateModUrl: string;

activateButton = this.getByRole("button", { name: "Activate" });
keyboardShortcutDocumentationLink = this.getByRole("link", {
name: "configuring keyboard shortcuts",
});

configureQuickbarShortcutLink = this.getByRole("link", {
name: "configured your Quick Bar",
});

constructor(
private readonly page: Page,
page: Page,
private readonly extensionId: string,
private readonly modId: string,
) {
super(page);
this.baseConsoleUrl = getBaseExtensionConsoleUrl(extensionId);
this.activateModUrl = `${
this.baseConsoleUrl
Expand All @@ -163,35 +164,21 @@ export class ActivateModPage {
async goto() {
await this.page.goto(this.activateModUrl);

await expect(this.page.getByText("Activate Mod")).toBeVisible();
await expect(this.getByText("Activate Mod")).toBeVisible();
// Loading the mod details may take more than 5 seconds
await expect(this.page.getByText(this.modId)).toBeVisible({
await expect(this.getByText(this.modId)).toBeVisible({
timeout: 10_000,
});
}

activateButton() {
return this.page.getByRole("button", { name: "Activate" });
}

configureQuickbarShortcutLink() {
return this.page.getByRole("link", { name: "configured your Quick Bar" });
}

keyboardShortcutDocumentationLink() {
return this.page.getByRole("link", {
name: "configuring keyboard shortcuts",
});
}

/** Successfully activating the mod will navigate to the "All Mods" page. */
async clickActivateAndWaitForModsPageRedirect() {
await this.activateButton().click();
await this.activateButton.click();
await this.page.waitForURL(`${this.baseConsoleUrl}#/mods`);
const modsPage = new ModsPage(this.page, this.extensionId);
await modsPage.viewActiveMods();
// Loading mods sometimes takes upwards of 5s
await expect(modsPage.modTableItems().getByText(this.modId)).toBeVisible({
// Loading mods sometimes takes upwards of 10s
await expect(modsPage.modTableItems.getByText(this.modId)).toBeVisible({
timeout: 10_000,
});
return modsPage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type Locator, type Page } from "@playwright/test";
import { WorkshopModEditor } from "./modEditor";
import { BasePageObject } from "../../basePageObject";

export class CreateWorkshopModPage {
readonly editor: WorkshopModEditor;
readonly createBrickButton: Locator;

constructor(private readonly page: Page) {
this.editor = new WorkshopModEditor(this.page);
this.createBrickButton = this.page.getByRole("button", {
name: "Create Brick",
});
}
export class CreateWorkshopModPage extends BasePageObject {
editor = new WorkshopModEditor(this.getByLabel("Editor"));
createBrickButton = this.getByRole("button", {
name: "Create Brick",
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type Page } from "@playwright/test";
import { WorkshopModEditor } from "./modEditor";
import { BasePageObject } from "end-to-end-tests/pageObjects/basePageObject";

export class EditWorkshopModPage {
readonly editor: WorkshopModEditor;
constructor(private readonly page: Page) {
this.editor = new WorkshopModEditor(this.page);
}
export class EditWorkshopModPage extends BasePageObject {
editor = new WorkshopModEditor(this.getByLabel("Editor"));

async updateBrick() {
await this.page.getByRole("button", { name: "Update Brick" }).click();
await this.getByRole("button", { name: "Update Brick" }).click();
}

async deleteBrick() {
await this.page.getByRole("button", { name: "Delete Brick" }).click();
await this.page.getByRole("button", { name: "Permanently Delete" }).click();
await this.getByRole("button", { name: "Delete Brick" }).click();
await this.getByRole("button", { name: "Permanently Delete" }).click();
// eslint-disable-next-line playwright/no-networkidle -- for some reason, can't assert on the "Brick deleted" notice
await this.page.waitForLoadState("networkidle");
}
Expand Down
26 changes: 10 additions & 16 deletions end-to-end-tests/pageObjects/extensionConsole/workshop/modEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type Locator, type Page, expect } from "@playwright/test";
import { expect } from "@playwright/test";
import fs from "node:fs/promises";
import path from "node:path";
import { uuidv4 } from "@/types/helpers";
import { BasePageObject } from "../../basePageObject";

export class WorkshopModEditor {
readonly textArea: Locator;
readonly content: Locator;
readonly baseLocator: Locator;

constructor(private readonly page: Page) {
this.baseLocator = this.page.getByLabel("Editor");
this.content = this.baseLocator.locator(".ace_content");
this.textArea = this.baseLocator.getByRole("textbox");
}
export class WorkshopModEditor extends BasePageObject {
content = this.locator(".ace_content");
textArea = this.getByRole("textbox");

async waitForLoad() {
await expect(this.textArea).toBeVisible();
await expect(this.baseLocator.getByText("Loading editor...")).toBeHidden();
await expect(this.getByText("Loading editor...")).toBeHidden();
}

async getValue() {
Expand All @@ -50,14 +44,14 @@ export class WorkshopModEditor {
async findText(text: string) {
await this.content.click(); // Focus on the visible editor
await this.page.keyboard.press("ControlOrMeta+f");
await this.page.getByPlaceholder("Search for").fill(text);
await this.getByPlaceholder("Search for").fill(text);
}

async findAndReplaceText(findText: string, replaceText: string) {
await this.findText(findText);
await this.page.getByText("+", { exact: true }).click();
await this.page.getByPlaceholder("Replace with").fill(replaceText);
await this.page.getByText("Replace").click();
await this.getByText("+", { exact: true }).click();
await this.getByPlaceholder("Replace with").fill(replaceText);
await this.getByText("Replace").click();
}

// Loads the corresponding yaml from the fixtures/modDefinitions directory
Expand Down
Loading

0 comments on commit 9d86d12

Please sign in to comment.