Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate screenshotting for the readme WD-11363 #776

Merged
merged 1 commit into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for my understanding, where do we get these uuids?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the screens to the description of this PR and then used the ids that github created in our readme.


| 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}`);
mas-who marked this conversation as resolved.
Show resolved Hide resolved
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
163 changes: 163 additions & 0 deletions tests/readme-screenshots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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.beforeAll(() => {
test.skip(
Boolean(process.env.CI),
"This suite is only run manually to create screenshots for the readme file",
);
});

test("instance creation screen", async ({ page }) => {
await page.goto("/ui/");
await page.getByText("Instances", { exact: true }).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", exact: true }).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 }) => {
await page.goto("/ui/");
const instance = "upright-pangolin";
await page.getByText("Instances", { exact: true }).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" });
});
Loading