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

Feat/Add e2e test with Playwright #161

Merged
merged 14 commits into from
Feb 22, 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
5 changes: 5 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ NEXT_PUBLIC_IPFS_ENDPOINT=https://api.neokingdom.org/ipfs/api/v0
# Cookies stuff
COOKIE_NAME=neokingdom
COOKIE_PASSWORD=ohQdJPDC9vN8e7cPMGsWWRqWjvwzTjjz

# E2E stuff
E2E_WALLET_ENDPOINT=test test test test test test test test test test test junk
E2E_ODOO_USERNAME=invalid
E2E_ODOO_PASSWORD=invalid
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ next-env.d.ts
/public/sw*.js*

.vscode/

# playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache//Users/atosatto/dev/test-playwright/tests

/.cache-synpress/
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,12 @@ Note: if you want to change from i.e. teledisko to neokingdom, just re-run `verc
- When the query is ready or after each update remember to run `pnpm codegen:subgraph`

If the schema on https://api.neokingdom.org/subgraphs/name/NeokingdomDAO/vigodarzere is not working - as the server is down - try with https://api2.neokingdom.org/subgraphs/name/NeokingdomDAO/vigodarzere. You can find in `./codegen-subgraph.ts`

## How to run e2e tests

You can run e2e tests with Playwright in two different ways. In both case remember to start the local server with `pnpm dev`.

The first time you should run `pnpm e2e:init` before running e2e tests. After that you can use one of the following options:

- With the CLI (faster): `pnpm e2e`
- With the UI (easier to debug): `pnpm e2e:ui`
2 changes: 1 addition & 1 deletion codegen-subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
overwrite: true,
schema: "https://api.neokingdom.org/subgraphs/name/NeokingdomDAO/vigodarzere",
schema: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT,
documents: "lib/graphql/subgraph/queries/**/*.tsx",
generates: {
"lib/graphql/subgraph/generated/": {
Expand Down
62 changes: 62 additions & 0 deletions e2e/fixtures/get-resolutions-legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { GetLegacyResolutionsQuery } from "@graphql/subgraph/generated/graphql";

export const legacyResolutionsMock: GetLegacyResolutionsQuery = {
resolutions: [
{
id: "100",
title: "Title Legacy Resolution 1",
content: "Content Legacy Resolution 1",
isNegative: false,
resolutionType: {
id: "8",
name: "30sNotice3mVoting",
quorum: "66",
noticePeriod: "30",
votingPeriod: "180",
canBeNegative: false,
},
yesVotesTotal: "0",
createTimestamp: "1707852970",
updateTimestamp: null,
approveTimestamp: null,
rejectTimestamp: "1707854760",
executionTimestamp: null,
createBy: "0x0000000000000000000000000000000000000001",
updateBy: null,
approveBy: null,
rejectBy: "0x0000000000000000000000000000000000000002",
hasQuorum: null,
executionTo: [],
executionData: [],
voters: [],
},
{
id: "101",
title: "Title Legacy Resolution 2",
content: "Content Legacy Resolution 2",
isNegative: false,
resolutionType: {
id: "6",
name: "routine",
quorum: "51",
noticePeriod: "259200",
votingPeriod: "172800",
canBeNegative: true,
},
yesVotesTotal: "0",
createTimestamp: "1707505249",
updateTimestamp: null,
approveTimestamp: null,
rejectTimestamp: null,
executionTimestamp: null,
createBy: "0x0000000000000000000000000000000000000003",
updateBy: null,
approveBy: null,
rejectBy: null,
hasQuorum: null,
executionTo: [],
executionData: [],
voters: [],
},
],
};
64 changes: 64 additions & 0 deletions e2e/fixtures/get-resolutions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { GetResolutionsQuery } from "@graphql/subgraph/generated/graphql";

export const resolutionsMock: GetResolutionsQuery = {
resolutions: [
{
id: "1",
title: "Title Resolution 1",
content: "Content Resolution 1",
isNegative: false,
resolutionType: {
id: "8",
name: "30sNotice3mVoting",
quorum: "66",
noticePeriod: "30",
votingPeriod: "180",
canBeNegative: false,
},
yesVotesTotal: "0",
createTimestamp: "1707852970",
updateTimestamp: null,
approveTimestamp: null,
rejectTimestamp: "1707854760",
executionTimestamp: null,
createBy: "0x0000000000000000000000000000000000000001",
updateBy: null,
approveBy: null,
rejectBy: "0x0000000000000000000000000000000000000002",
hasQuorum: null,
executionTo: [],
executionData: [],
addressedContributor: "0x0000000000000000000000000000000000000000",
voters: [],
},
{
id: "2",
title: "Title Resolution 2",
content: "Content Resolution 2",
isNegative: false,
resolutionType: {
id: "6",
name: "routine",
quorum: "51",
noticePeriod: "259200",
votingPeriod: "172800",
canBeNegative: true,
},
yesVotesTotal: "0",
createTimestamp: "1707505249",
updateTimestamp: null,
approveTimestamp: null,
rejectTimestamp: null,
executionTimestamp: null,
createBy: "0x0000000000000000000000000000000000000003",
updateBy: null,
approveBy: null,
rejectBy: null,
hasQuorum: null,
executionTo: [],
executionData: [],
addressedContributor: "0x0000000000000000000000000000000000000000",
voters: [],
},
],
};
64 changes: 64 additions & 0 deletions e2e/testWithMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { type TypedDocumentNode } from "@graphql-typed-document-node/core";
import { Page, Route, test as baseTest, expect } from "@playwright/test";

// original from https://www.jayfreestone.com/writing/stubbing-graphql-playwright/
export async function interceptGQL<TResult, TVariables>(
page: Page,
document: TypedDocumentNode<TResult, TVariables>,
resp: TResult,
isLegacy?: boolean,
): Promise<TVariables[]> {
// A list of GQL variables which the handler has been called with.
const reqs: TVariables[] = [];

// Register a new handler which intercepts all GQL requests.
await page.route(
isLegacy ? process.env.NEXT_PUBLIC_LEGACY_GRAPHQL_ENDPOINT || "" : process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT,
function (route: Route) {
const req = route.request().postDataJSON();
// @ts-ignore wrong types from library
const documentOperationName = document.definitions[0]["name"]["value"] as string;

// Pass along to the previous handler in the chain if the request
// is for a different operation.
if (req.operationName !== documentOperationName) {
return route.fallback();
}

// Store what variables we called the API with.
reqs.push(req.variables);
return route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: resp }),
});
},
);

return reqs;
}

const test = baseTest.extend<{ interceptGQL: typeof interceptGQL }>({
interceptGQL: async ({}, use) => {
await use(interceptGQL);
},
page: async ({ page }, use) => {
await page.route(process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT, function (route: Route) {
const req = route.request().postDataJSON();
console.warn(`No mock provided for public graphql request: ${req.operationName}`);
route.continue();
});

if (process.env.NEXT_PUBLIC_LEGACY_GRAPHQL_ENDPOINT) {
await page.route(process.env.NEXT_PUBLIC_LEGACY_GRAPHQL_ENDPOINT, function (route: Route) {
const req = route.request().postDataJSON();
console.warn(`No mock provided for public legacy graphql request: ${req.operationName}`);
route.continue();
});
}

await use(page);
},
});

export { test, expect };
52 changes: 52 additions & 0 deletions e2e/tests/login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect, test } from "@playwright/test";
import { MetaMask, testWithSynpress, unlockForFixture } from "@synthetixio/synpress";

import BasicSetup from "../wallet-setup/basic.setup";

const walletTest = testWithSynpress(BasicSetup, unlockForFixture);
const { expect: walletExpect } = test;

test("login with just Odoo", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Log in with username and password" }).click();

await page.getByLabel("Odoo Username *").fill(process.env.E2E_ODOO_USERNAME);
await page.getByLabel("Odoo Password *").fill(process.env.E2E_ODOO_PASSWORD);

await page.getByRole("button", { name: "Log in" }).click();
await expect(
page.getByText(
"You are currently just connected through Odoo. Please connect your wallet for seamless interaction within the dapp.",
),
).toBeVisible();
});

// see https://github.com/Synthetixio/synpress/issues/1103
walletTest.fixme("login just with the wallet (account in DAO)", async ({ context, page, extensionId }) => {
const metamask = new MetaMask(context, page, BasicSetup.walletPassword, extensionId);

await page.goto("http://localhost:3000/");
await page.getByRole("button", { name: "Connect wallet" }).click();
await page.getByRole("button", { name: "MetaMask MetaMask" }).first().click();
await metamask.connectToDapp();

await walletExpect(page.getByRole("button", { name: "Log in to odoo with wallet" })).toBeVisible();
await walletExpect(page.getByRole("button", { name: "Log in with username and" })).toBeVisible();
});

// see https://github.com/Synthetixio/synpress/issues/1103
walletTest.fixme("login with both the wallet and Odoo", async ({ context, page, extensionId }) => {
const metamask = new MetaMask(context, page, BasicSetup.walletPassword, extensionId);

await page.goto("http://localhost:3000/");
await page.getByRole("button", { name: "Connect wallet" }).click();
await page.getByRole("button", { name: "MetaMask MetaMask" }).first().click();
await metamask.connectToDapp();

page.getByRole("button", { name: "Log in with username and password" }).click();
await page.getByLabel("Odoo Username *").fill(process.env.E2E_ODOO_USERNAME);
await page.getByLabel("Odoo Password *").fill(process.env.E2E_ODOO_PASSWORD);

await page.getByRole("button", { name: "Log in" }).click();
await walletExpect(page.getByRole("heading", { name: "Resolutions stats" })).toBeVisible();
});
16 changes: 16 additions & 0 deletions e2e/tests/main-menu.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { expect, test } from "@playwright/test";

test("get shareholders link", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("link", { name: "Shareholders" })).toBeVisible();
});

test("get resolutions link", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("link", { name: "Resolutions" })).toBeVisible();
});

test("get IBC tool link", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("link", { name: "IBC tool" })).toBeVisible();
});
20 changes: 20 additions & 0 deletions e2e/tests/resolutions-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { GetLegacyResolutionsDocument, GetResolutionsDocument } from "@graphql/subgraph/generated/graphql";

import { resolutionsMock } from "../fixtures/get-resolutions";
import { legacyResolutionsMock } from "../fixtures/get-resolutions-legacy";
import { expect, test } from "../testWithMock";

test("should show at least one resolution", async ({ page, interceptGQL }) => {
await interceptGQL(page, GetResolutionsDocument, resolutionsMock);
await interceptGQL(page, GetLegacyResolutionsDocument, legacyResolutionsMock, true);

await page.goto("/");
await page.getByRole("link", { name: "Resolutions" }).click();

await expect(page.getByRole("link", { name: "Title resolution 2" })).toBeVisible();
await expect(page.getByRole("link", { name: "Title resolution 1" })).not.toBeVisible();

// legacy resolutions
await expect(page.getByRole("link", { name: "Title Legacy Resolution 2" })).toBeVisible();
await expect(page.getByRole("link", { name: "Title Legacy Resolution 1" })).not.toBeVisible();
});
18 changes: 18 additions & 0 deletions e2e/wallet-setup/basic.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MetaMask, defineWalletSetup } from "@synthetixio/synpress";
import "dotenv/config";

const DEFAULT_SEED_PHRASE = "test test test test test test test test test test test junk";
const PASSWORD = "SynpressIsAwesomeNow!!!";

export default defineWalletSetup(PASSWORD, async (context, walletPage) => {
const metamask = new MetaMask(context, walletPage, PASSWORD);

await metamask.importWallet(process.env.E2E_WALLET_ENDPOINT || DEFAULT_SEED_PHRASE);

await metamask.addNetwork({
name: "Mumbai",
rpcUrl: "https://rpc-mumbai.maticvigil.com",
chainId: 80001,
symbol: "MATIC",
});
});
3 changes: 3 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ namespace NodeJS {
NEXT_PUBLIC_ENV: "staging" | "production";
COOKIE_NAME: string;
COOKIE_PASSWORD: string;
E2E_WALLET_ENDPOINT: string;
E2E_ODOO_USERNAME: string;
E2E_ODOO_PASSWORD: string;
}
}
File renamed without changes.
Loading