Skip to content

Commit

Permalink
feat(docs) automate screenshotting for the readme WD-11363
Browse files Browse the repository at this point in the history
Signed-off-by: David Edler <david.edler@canonical.com>
  • Loading branch information
edlerd committed May 22, 2024
1 parent 487ba59 commit 4977467
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,6 @@ dist/
/playwright-report/
/playwright/.cache/
/coverage
tests/screenshots

haproxy-local.cfg
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ LXD-UI is a single page application written in TypeScript and React. See [Archit

| Create an instance | Instance list | Instance terminal |
|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
| ![0create](https://github.com/canonical/lxd-ui/assets/1155472/7f0c45a6-2ba2-4cc7-bd7c-c0ebca76d648) | ![1instance-overview](https://github.com/canonical/lxd-ui/assets/1155472/c71d2153-ea71-4ecb-ab25-fabcd6fb1e55) | ![2instance-term](https://github.com/canonical/lxd-ui/assets/1155472/c2b741e2-8806-4d4d-9a9a-f536f76a13b9) |
| ![0create](https://github.com/canonical/lxd-ui/assets/1155472/8c4f5eee-9d5a-40ca-93e1-57b1c393dbd9) | ![1instance-overview](https://github.com/canonical/lxd-ui/assets/1155472/af4a92ce-e562-43eb-945f-98b78b4bb03e) | ![2instance-term](https://github.com/canonical/lxd-ui/assets/1155472/14eaaffb-c770-4f34-936f-075ceb6be42e) |

| Graphic console | Profile list | Cluster groups |
|----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| ![3-instance-console](https://github.com/canonical/lxd-ui/assets/1155472/0f8d742d-3f9c-4906-90da-e740e8ff353b) | ![profile-list](https://github.com/canonical/lxd-ui/assets/1155472/36a0f619-767f-4949-804d-061e5e28c87a) | ![6cluster](https://github.com/canonical/lxd-ui/assets/1155472/85f61ef9-a45f-4b4a-abee-8fa9dfa69bd2) |
| ![3-instance-console](https://github.com/canonical/lxd-ui/assets/1155472/e3301135-e737-4f7f-8bfb-1297135402a4) | ![profile-list](https://github.com/canonical/lxd-ui/assets/1155472/f19c3d70-5c25-47b0-9c8e-636bfa42fabe) | ![6cluster](https://github.com/canonical/lxd-ui/assets/1155472/85f61ef9-a45f-4b4a-abee-8fa9dfa69bd2) |

| Storage | Operations | Warnings |
|-------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| ![5storage](https://github.com/canonical/lxd-ui/assets/1155472/38d7b8ab-d652-4c18-b71e-0098efe73702) | ![operations](https://github.com/canonical/lxd-ui/assets/1155472/d3168891-19fb-4724-95cb-9afc91191555) | ![warnings](https://github.com/canonical/lxd-ui/assets/1155472/56499dfc-15a2-4c59-8761-47709b4be957) |
| ![5storage](https://github.com/canonical/lxd-ui/assets/1155472/d78759a6-9e54-41d4-b9b9-f9905c550763) | ![operations](https://github.com/canonical/lxd-ui/assets/1155472/c8dfd5c8-5634-4d2e-9167-204c730df574) | ![warnings](https://github.com/canonical/lxd-ui/assets/1155472/a22334c8-61b8-4f8a-b49e-b4d4ad311285) |
26 changes: 18 additions & 8 deletions tests/helpers/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export const randomInstanceName = (): string => {
export const createInstance = async (
page: Page,
instance: string,
type = "container",
type: string = "container",
project: string = "default",
image: string = "alpine/3.19/cloud",
) => {
await page.goto("/ui/");
await page.goto(`/ui/project/${project}`);
await page
.getByRole("link", { name: "Instances", exact: true })
.first()
Expand All @@ -20,8 +22,8 @@ export const createInstance = async (
await page.getByLabel("Instance name").fill(instance);
await page.getByRole("button", { name: "Browse images" }).click();
await page.getByPlaceholder("Search an image").click();
await page.getByPlaceholder("Search an image").fill("alpine/3.19/cloud");
await page.getByRole("button", { name: "Select" }).click();
await page.getByPlaceholder("Search an image").fill(image);
await page.getByRole("button", { name: "Select" }).first().click();
await page
.getByRole("combobox", { name: "Instance type" })
.selectOption(type);
Expand All @@ -30,8 +32,12 @@ export const createInstance = async (
await page.waitForSelector(`text=Created instance ${instance}.`);
};

export const visitInstance = async (page: Page, instance: string) => {
await page.goto("/ui/");
export const visitInstance = async (
page: Page,
instance: string,
project: string = "default",
) => {
await page.goto(`/ui/project/${project}`);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill(instance);
await page.getByRole("link", { name: instance }).first().click();
Expand All @@ -49,8 +55,12 @@ export const saveInstance = async (page: Page, instance: string) => {
await page.getByRole("button", { name: "Close notification" }).click();
};

export const deleteInstance = async (page: Page, instance: string) => {
await visitInstance(page, instance);
export const deleteInstance = async (
page: Page,
instance: string,
project: string = "default",
) => {
await visitInstance(page, instance, project);
const stopButton = page.getByRole("button", { name: "Stop", exact: true });
if (await stopButton.isEnabled()) {
await page.keyboard.down("Shift");
Expand Down
32 changes: 24 additions & 8 deletions tests/helpers/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ export const randomProfileName = (): string => {
return `playwright-profile-${randomNameSuffix()}`;
};

export const createProfile = async (page: Page, profile: string) => {
await startProfileCreation(page, profile);
export const createProfile = async (
page: Page,
profile: string,
project: string = "default",
) => {
await startProfileCreation(page, profile, project);
await finishProfileCreation(page, profile);
};

export const startProfileCreation = async (page: Page, profile: string) => {
await page.goto("/ui/");
export const startProfileCreation = async (
page: Page,
profile: string,
project: string = "default",
) => {
await page.goto(`/ui/project/${project}`);
await page.getByRole("link", { name: "Profiles" }).click();
await page.getByRole("button", { name: "Create profile" }).click();
await page.getByLabel("Profile name").fill(profile);
Expand All @@ -22,8 +30,12 @@ export const finishProfileCreation = async (page: Page, profile: string) => {
await page.waitForSelector(`text=Profile ${profile} created.`);
};

export const deleteProfile = async (page: Page, profile: string) => {
await visitProfile(page, profile);
export const deleteProfile = async (
page: Page,
profile: string,
project: string = "default",
) => {
await visitProfile(page, profile, project);
await page.getByRole("button", { name: "Delete" }).click();
await page
.getByRole("dialog", { name: "Confirm delete" })
Expand All @@ -32,8 +44,12 @@ export const deleteProfile = async (page: Page, profile: string) => {
await page.waitForSelector(`text=Profile ${profile} deleted.`);
};

export const visitProfile = async (page: Page, profile: string) => {
await page.goto("/ui/");
export const visitProfile = async (
page: Page,
profile: string,
project: string = "default",
) => {
await page.goto(`/ui/project/${project}`);
await page.getByRole("link", { name: "Profiles" }).click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill(profile);
Expand Down
158 changes: 158 additions & 0 deletions tests/readme-screenshots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { expect, test } from "./fixtures/lxd-test";
import {
createInstance,
deleteInstance,
visitAndStartInstance,
} from "./helpers/instances";
import { createProject, deleteProject } from "./helpers/projects";
import { createProfile, deleteProfile, visitProfile } from "./helpers/profile";

test("instance creation screen", async ({ page }) => {
await page.goto("/ui/");
await page.getByText("Instances").click();
await page.getByText("Create instance").click();
await page.getByPlaceholder("Enter name").fill("comic-glider");
await page.getByRole("button", { name: "Browse images" }).click();
await page
.locator("tr")
.filter({ hasText: "Ubuntu24.04 LTS" })
.first()
.getByRole("button")
.click();

await page.screenshot({ path: "tests/screenshots/create-instance.png" });
});

test("instance list screen", async ({ page }) => {
await page.goto("/ui/");
const project = "my-cluster";
await createProject(page, project);
await visitProfile(page, "default", project);
await page.getByTestId("tab-link-Configuration").click();
await page.getByRole("button", { name: "Edit profile" }).click();
await page.getByText("Disk devices").click();
await page.getByRole("button", { name: "Create override" }).click();
await page.getByRole("button", { name: "Save changes" }).click();
const instances = [
"comic-glider",
"deciding-flounder",
"native-sailfish",
"precise-lacewing",
"ready-grizzly",
"singular-moose",
];
for (const instance of instances) {
await createInstance(page, instance, "container", project, "24.04");
}
await page.goto(`/ui/project/${project}`);
await page
.getByRole("row", {
name: "Select comic-glider Name Type Description Status Actions",
})
.getByLabel("Type")
.click();

await page.screenshot({ path: "tests/screenshots/instance-list.png" });

for (const instance of instances) {
await deleteInstance(page, instance, project);
}
await page.getByRole("link", { name: "Images" }).click();
await page.getByRole("button", { name: "Delete", exact: true }).click();
await page.getByText("Delete", { exact: true }).click();
await deleteProject(page, project);
});

test("instance terminal screen", async ({ page }) => {
await page.goto("/ui/");
const instance = "comic-glider";
await createInstance(page, instance, "container", "default", "24.04");
await visitAndStartInstance(page, instance);
await page.getByRole("button", { name: "Close notification" }).click();
await page.getByTestId("tab-link-Terminal").click();
await expect(page.getByText("~#")).toBeVisible();
await page.waitForTimeout(1000); // ensure the terminal is ready
await page.keyboard.type("cd /");
await page.keyboard.press("Enter");
await page.keyboard.type("ll");
await page.keyboard.press("Enter");
await page.keyboard.type("cat /etc/issue");
await page.keyboard.press("Enter");

await page.screenshot({ path: "tests/screenshots/instance-terminal.png" });

await deleteInstance(page, instance);
});

test("instance graphical console screen", async ({ page }) => {
test.skip(Boolean(process.env.CI), "github runners lack vm support");

await page.goto("/ui/");
const instance = "upright-pangolin";
await page.getByText("Instances").click();
await page.getByText("Create instance").click();
await page.getByPlaceholder("Enter name").fill(instance);
await page.getByRole("button", { name: "Browse images" }).click();
await page
.locator("tr")
.filter({ hasText: "ubuntu/24.04/desktop" })
.first()
.getByRole("button")
.click();
await page.getByRole("button", { name: "Create", exact: true }).click();
await visitAndStartInstance(page, instance);
await page.getByRole("button", { name: "Close notification" }).click();
await page.getByTestId("tab-link-Console").click();
await page.waitForTimeout(40000); // ensure the vm is booted

await page.screenshot({
path: "tests/screenshots/instance-graphical-console.png",
});

await deleteInstance(page, instance);
});

test("profile list screen", async ({ page }) => {
await page.goto("/ui/");
const project = "my-cluster";
await createProject(page, project);
await createProfile(page, "small", project);
await createProfile(page, "medium", project);
await createProfile(page, "large", project);
await page.goto(`/ui/project/${project}/profiles`);

await page.screenshot({ path: "tests/screenshots/profile-list.png" });

await deleteProfile(page, "small", project);
await deleteProfile(page, "medium", project);
await deleteProfile(page, "large", project);
await deleteProject(page, project);
});

test("storage pool screen", async ({ page }) => {
await page.goto("/ui/");
await page.getByText("Storage").click();
await page.getByText("Pools").click();
await page.getByText("Created").first().click();

await page.screenshot({ path: "tests/screenshots/storage-pool-list.png" });
});

test("operations screen", async ({ page }) => {
await page.goto("/ui/");
await createInstance(page, "comic-glider");
await page.getByRole("button", { name: "Close notification" }).click();
await page.getByText("Operations").click();
await page.getByText("Creating instance").first().click();

await page.screenshot({ path: "tests/screenshots/operations-list.png" });

await deleteInstance(page, "comic-glider");
});

test("warnings screen", async ({ page }) => {
await page.goto("/ui/");
await page.getByText("Warnings").click();

await page.screenshot({ path: "tests/screenshots/warnings-list.png" });
});

0 comments on commit 4977467

Please sign in to comment.