-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/Add e2e test with Playwright (#161)
# Description Part of #157 Add Playwrigh and add the first few tests. The main goals are: - [x] Add Playwright and make it work locally - [x] Add a few tests and confirm they are working correctly (with localhost) - [x] Support connection to Metamask - [x] Add a test with API mocks, specifically for subgraph - [x] Add a test with Odoo and Wallet connection - [x] Make the API mocks typesafe - [x] Add documentation⚠️ There is an issue with how the wallet is handled after being cached for the e2e test: Synthetixio/synpress#1103 ### A few notes: I started mocking with MSW but it was a painful experience. First, Playwright doesn't work well with libraries that [override their service worker ](https://playwright.dev/docs/network#missing-network-events-and-service-workers). Then, the most promising plugin to support graphql-codegen with MSW and Playwright does not support multiple endpoints, which is an issue for testing the legacy flow (like the resolutions). In the end, I'm fairly happy with the final solution. It's not a lot of code and it doesn't use new libraries but reuses what we have without sacrificing functionalities. --------- Co-authored-by: Andrea Tosatto <[email protected]>
- Loading branch information
Showing
21 changed files
with
2,945 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: [], | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: [], | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.