Skip to content

Commit

Permalink
feat: synpress plugin for cypress
Browse files Browse the repository at this point in the history
  • Loading branch information
matstyler committed Mar 30, 2024
1 parent 1b28bbd commit f5375e8
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 50 deletions.
4 changes: 2 additions & 2 deletions docs/docs/guides/playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Let's digest a simple test step by step:
::: code-group
```typescript [example.spec.ts]
import { MetaMask, testWithSynpress, unlockForFixture } from '@synthetixio/synpress'
import BasicSetup from '../playwright/wallet-setup/basic.setup'
import BasicSetup from '../wallet-setup/basic.setup'

const test = testWithSynpress(BasicSetup, unlockForFixture)

Expand All @@ -34,7 +34,7 @@ First, you need to import the `testWithSynpress` function from `@synthetixio/syn

```typescript
import { MetaMask, testWithSynpress, unlockForFixture } from '@synthetixio/synpress'
import BasicSetup from '../playwright/wallet-setup/basic.setup'
import BasicSetup from '../wallet-setup/basic.setup'

const test = testWithSynpress(BasicSetup, unlockForFixture)
```
Expand Down
2 changes: 1 addition & 1 deletion examples/new-dawn/test/e2e/01_basic.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MetaMask, testWithSynpress, unlockForFixture } from '@synthetixio/synpress'
import BasicSetup from '../playwright/wallet-setup/basic.setup'
import BasicSetup from '../wallet-setup/basic.setup';

const test = testWithSynpress(BasicSetup, unlockForFixture)

Expand Down
38 changes: 12 additions & 26 deletions wallets/metamask/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import { defineConfig } from 'cypress'
import { prepareExtension } from './src/prepareExtension'
import { defineConfig } from "cypress";
import { installSynpress } from "./src/cypress";

export default defineConfig({
chromeWebSecurity: false,
e2e: {
baseUrl: 'http://localhost:9999',
supportFile: 'src/support/e2e.{js,jsx,ts,tsx}',
specPattern: 'test/**/*.cy.{js,jsx,ts,tsx}',
fixturesFolder: 'src/fixture-actions',
baseUrl: "http://localhost:9999",
supportFile: "src/cypress/support/e2e.{js,jsx,ts,tsx}",
specPattern: "test/**/*.cy.{js,jsx,ts,tsx}",
fixturesFolder: "src/cypress/fixtures",
testIsolation: false,
setupNodeEvents(on, config) {
const browsers = config.browsers.filter((b) => b.name === 'chrome')
if (browsers.length === 0) {
throw new Error('No Chrome browser found in the configuration')
}

on('before:browser:launch', async (browser, launchOptions) => {
const metamasExtensionPath = await prepareExtension()
launchOptions.extensions.push(metamasExtensionPath)

return launchOptions
})

return {
...config,
browsers
}
}
}
})
async setupNodeEvents(on, config) {
return installSynpress(on, config);
},
},
});
2 changes: 1 addition & 1 deletion wallets/metamask/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"test:e2e:headful": "playwright test",
"test:e2e:headful:cypress": "cypress run --browser chrome --headed",
"test:e2e:headless": "HEADLESS=true playwright test",
"test:e2e:headless:cypress": "cypress run --browser chrome",
"test:e2e:headless:cypress": "cypress run --headless --browser chrome",
"test:e2e:headless:ui": "HEADLESS=true playwright test --ui",
"test:watch": "vitest watch",
"types:check": "tsc --noEmit"
Expand Down
4 changes: 4 additions & 0 deletions wallets/metamask/src/cypress/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const NO_CONTEXT =
"No browser context found. Connect Playwright first - connectPlaywright()";
export const NO_METAMASK_PAGE = "No MetaMask page found. Use getMetaMaskPage()";
export const MISSING_INIT = "MetaMask not initialized. Use initMetaMask()";
3 changes: 3 additions & 0 deletions wallets/metamask/src/cypress/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./initMetaMask";
export { default as setupTasks } from "./setupTasks";
export { default as installSynpress } from "./installSynpress";
83 changes: 83 additions & 0 deletions wallets/metamask/src/cypress/initMetaMask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { type BrowserContext, type Page, chromium } from "@playwright/test";
import { getExtensionId } from "@synthetixio/synpress-fixtures";
import { PASSWORD, SEED_PHRASE } from "../constants";
import { MetaMask } from "../metamask";
import { MISSING_INIT, NO_CONTEXT, NO_METAMASK_PAGE } from "./errors";

let context: BrowserContext | undefined;
let extensionId: string | undefined;
let metamaskPage: Page | undefined;
let metamask: MetaMask | undefined;

export async function getMetaMaskExtensionId() {
if (extensionId) return extensionId;

if (!context) {
console.error(NO_CONTEXT);
return;
}

extensionId = await getExtensionId(context, "MetaMask");
return extensionId;
}

const isMetaMaskPage = (page: Page) =>
page.url().includes(`chrome-extension://${extensionId}`);

const getMetaMaskPage = async () => {
await getMetaMaskExtensionId();

if (!context) {
console.error(NO_CONTEXT);
return;
}

metamaskPage = context.pages().find(isMetaMaskPage);
return metamaskPage;
};

export async function connectPlaywrightToChrome(port: number) {
const debuggerDetails = await fetch(`http://127.0.0.1:${port}/json/version`);

const debuggerDetailsConfig = (await debuggerDetails.json()) as {
webSocketDebuggerUrl: string;
};

const browser = await chromium.connectOverCDP(
debuggerDetailsConfig.webSocketDebuggerUrl
);

context = browser.contexts()[0];

return browser.isConnected();
}

export async function initMetaMask(port: number) {
await connectPlaywrightToChrome(port);

if (!context) {
console.error(NO_CONTEXT);
return;
}

await getMetaMaskPage();

if (!metamaskPage) {
console.error(NO_METAMASK_PAGE);
return;
}

metamask = new MetaMask(context, metamaskPage, PASSWORD);
await metamask.importWallet(SEED_PHRASE);
}

export function getMetaMask() {
if (!context || !metamaskPage || !metamask) {
console.error(MISSING_INIT);
return;
}

if (metamask) return metamask;

Check warning

Code scanning / CodeQL

Useless conditional Warning

This use of variable 'metamask' always evaluates to true.

return new MetaMask(context, metamaskPage, PASSWORD);
}
58 changes: 58 additions & 0 deletions wallets/metamask/src/cypress/installSynpress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { initMetaMask, setupTasks } from ".";
import { prepareExtension } from "../prepareExtension";

let port = 0;

function ensureRdpPort(args: string[]) {
const existing = args.find(
(arg) => arg.slice(0, 23) === "--remote-debugging-port"
);

if (existing) {
return Number(existing.split("=")[1]);
}

const port = 40000 + Math.round(Math.random() * 25000);

args.push(`--remote-debugging-port=${port}`);

return port;
}

export default function installSynpress(
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
) {
const browsers = config.browsers.filter((b) => b.name === "chrome");
if (browsers.length === 0) {
throw new Error("No Chrome browser found in the configuration");
}

on("before:browser:launch", async (_, launchOptions) => {
// Enable debug mode to establish playwright connection
const args = Array.isArray(launchOptions)
? launchOptions
: launchOptions.args;
port = ensureRdpPort(args);

// Preserved cache is not supported for Cypress - https://docs.cypress.io/guides/guides/launching-browsers#Cypress-Profile
// launchOptions.args.push('--user-data-dir=X')

// Add MetaMask extension
const metamaskExtensionPath = await prepareExtension();
launchOptions.extensions.push(metamaskExtensionPath);

return launchOptions;
});

on("before:spec", async () => {
await initMetaMask(port);
});

setupTasks(on);

return {
...config,
browsers,
};
}
34 changes: 34 additions & 0 deletions wallets/metamask/src/cypress/setupTasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getMetaMask } from ".";

export default function setupTasks(on: Cypress.PluginEvents) {
on("task", {
importWallet: async function (seedPhrase: string) {
const metamask = getMetaMask();
if (metamask) {
await metamask.importWallet(seedPhrase);
}
return true;
},
addNewAccount: async function (accountName: string) {
const metamask = getMetaMask();
if (metamask) {
await metamask.addNewAccount(accountName);
}
return true;
},
importWalletFromPrivateKey: async function (privateKey: string) {
const metamask = getMetaMask();
if (metamask) {
await metamask.importWalletFromPrivateKey(privateKey);
}
return true;
},
openSettings: async function () {
const metamask = getMetaMask();
if (metamask) {
await metamask.openSettings();
}
return true;
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,26 @@
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
// import { MetaMask } from "../metamask";
// import { PASSWORD } from "../constants";

declare global {
namespace Cypress {
interface Chainable {
importWallet(seedPhrase: string): Chainable<void>;
addNewAccount(accountName: string): Chainable<void>;
importWalletFromPrivateKey(privateKey: string): Chainable<void>;
openSettings(): Chainable<void>;
}
}
}
Cypress.Commands.add("importWallet", (seedPhrase) =>
cy.task("importWallet", seedPhrase)
);
Cypress.Commands.add("addNewAccount", (accountName) =>
cy.task("addNewAccount", accountName)
);
Cypress.Commands.add("importWalletFromPrivateKey", (privateKey) =>
cy.task("importWalletFromPrivateKey", privateKey)
);
Cypress.Commands.add("openSettings", () => cy.task("openSettings"));
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,3 @@

// Import commands.js using ES2015 syntax:
import './commands'

// TODO: Add MetaMask initial setup here.
7 changes: 4 additions & 3 deletions wallets/metamask/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './metamask'
export * from './fixture-actions/unlockForFixture'
export { default as homePageSelectors } from './pages/HomePage/selectors'
export * from "./metamask";
export * from "./fixture-actions/unlockForFixture";
export * from "./cypress";
export { default as homePageSelectors } from "./pages/HomePage/selectors";
5 changes: 0 additions & 5 deletions wallets/metamask/test/e2e/cypress/addNetwork.cy.ts

This file was deleted.

11 changes: 11 additions & 0 deletions wallets/metamask/test/e2e/cypress/metamask/setupMetaMask.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
it("should add new MetaMask account", () => {
cy.addNewAccount("Synpress with Cypress test");

cy.wait(10000);
});

it("should open MetaMask settings", () => {
cy.openSettings();

cy.wait(10000);
});

0 comments on commit f5375e8

Please sign in to comment.