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

[ECO-2362] Fix flaky playwright tests #322

Merged
merged 28 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
46f3fb2
Add configuration/package.json script and docker fixes
xbtmatt Nov 4, 2024
a57e54d
Add flaky test fixes
xbtmatt Nov 4, 2024
cc8e8ff
Format/lint
xbtmatt Nov 4, 2024
713c3de
Wait for sort order to change
xbtmatt Nov 4, 2024
fa5046b
Sleep after click, not after locator
xbtmatt Nov 4, 2024
d227d52
Don't run tests in parallel in CI, make wait time 1000
xbtmatt Nov 4, 2024
e735b7e
Test commit
xbtmatt Nov 4, 2024
bfba283
Test commit2
xbtmatt Nov 4, 2024
fd3e4dd
Try waiting for visible
xbtmatt Nov 4, 2024
795d327
Fix visibility check
xbtmatt Nov 4, 2024
dc96da4
Fix strict mode check
xbtmatt Nov 4, 2024
2a0794a
Run playwright in headed (non-headless) mode
xbtmatt Nov 4, 2024
6e6592f
Run in headless mode but skip market-order.spec.ts
xbtmatt Nov 4, 2024
76e4dab
Upload screenshots as artifacts upon failure
xbtmatt Nov 4, 2024
d76dc43
Add firefox dependencies so we can run in headed mode
xbtmatt Nov 4, 2024
e17a313
Properly pass args to ci command
xbtmatt Nov 4, 2024
285dc13
Add proper playwright config to the github action
xbtmatt Nov 4, 2024
e9f4cb7
Fix proper privileges with playwright docker container
xbtmatt Nov 4, 2024
887daf9
Remove --with-deps from package.json command
xbtmatt Nov 4, 2024
298873d
Undo using container/non-sudo
xbtmatt Nov 4, 2024
64692af
Run in headless mode
xbtmatt Nov 4, 2024
60d5354
Always add html reporter always
xbtmatt Nov 5, 2024
68bbdc5
Try trace retain on failure
xbtmatt Nov 5, 2024
c928b6b
Leave 2 retries in CI, but retain trace on first failure
xbtmatt Nov 5, 2024
84d8f51
Add contingent register to accomodate test being retried
xbtmatt Nov 5, 2024
b54f8fb
Remove space in playwright-report output folder
xbtmatt Nov 5, 2024
850a355
Lint
xbtmatt Nov 5, 2024
410f2d4
Lint
xbtmatt Nov 5, 2024
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
13 changes: 13 additions & 0 deletions .github/workflows/frontend-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ jobs:
run: 'pnpm run playwright:install'
- name: 'Run Playwright tests'
run: 'pnpm run test:frontend'
- if: '${{ failure() }}'
name: 'Upload screenshots as artifacts'
uses: 'actions/upload-artifact@v4'
with:
name: 'playwright-screenshots'
path: 'src/typescript/screenshots/'
- if: '${{ !cancelled() }}'
name: 'Upload Playwright report as artifact'
uses: 'actions/upload-artifact@v4'
with:
name: 'playwright-report'
path: 'src/typescript/playwright-report/'
retention-days: 30
timeout-minutes: 15
name: 'Run the frontend tests'
"on":
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
**/.env
**/.turbo
11 changes: 7 additions & 4 deletions src/typescript/frontend/playwright.config.ts
xbtmatt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { defineConfig, devices } from "@playwright/test";
*/
export default defineConfig({
testDir: "./tests/e2e",
/* Run tests in files in parallel */
/* Run tests in files in parallel. */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
Expand All @@ -14,20 +14,24 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.GITHUB_ACTIONS ? "github" : "list",
reporter: [
[process.env.GITHUB_ACTIONS ? "github" : "list"],
["html", { outputFolder: "playwright-report" }],
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://127.0.0.1:3001",

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
trace: "retain-on-first-failure",

launchOptions: {
env: {
...process.env,
NODE_OPTIONS: `${process.env.NODE_OPTIONS || ""} --conditions=react-server`,
},
slowMo: 0, // Change this to 1000-3000 to slow the test down and see what's going on.
},
},

Expand All @@ -47,7 +51,6 @@ export default defineConfig({
use: { ...devices["Desktop Chrome"] },
dependencies: ["setup"],
},

{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
Expand Down
24 changes: 16 additions & 8 deletions src/typescript/frontend/tests/e2e/market-order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ test("check sorting order", async ({ page }) => {
const markets = emojis.map((e) => [rat, SYMBOL_EMOJI_DATA.byName(e)!.emoji]);

const client = new EmojicoinClient();
client.view;

// Register markets.
// They all start with rat to simplify the search.
// Only register if it doesn't exist- this will occur on retries of the test.
for (let i = 0; i < markets.length; i++) {
await client.register(user, markets[i]).then((res) => res.handle);
const exists = await client.view.marketExists(markets[i]);
if (!exists) {
await client.register(user, markets[i]);
}
const amount = ((1n * ONE_APT_BIGINT) / 100n) * BigInt(10 ** (markets.length - i));
await client.buy(user, markets[i], amount).then((res) => res.handle);
await client.buy(user, markets[i], amount);
}

await page.goto("/home");
Expand Down Expand Up @@ -52,6 +57,7 @@ test("check sorting order", async ({ page }) => {

// Expect the sort by daily volume button to be visible.
const dailyVolume = page.locator("#emoji-grid-header").getByText("24h Volume");
await dailyVolume.waitFor({ state: "visible", timeout: 5000 });
expect(dailyVolume).toBeVisible();

// Sort by daily volume.
Expand All @@ -61,24 +67,26 @@ test("check sorting order", async ({ page }) => {
const patterns = names.map((e) => new RegExp(e));

// Expect the markets to be in order of daily volume.
marketGridItems = page.locator("#emoji-grid a").getByTitle(/RAT,/, { exact: true });
marketGridItems = page.locator("#emoji-grid a").getByTitle(/RAT,/);
expect(marketGridItems).toHaveText(patterns);

// Click the sorting button.
await filters.click();

// Expect the sort by bump order button to be visible.
const bumpOrder = page.locator("#emoji-grid-header").getByText("Bump Order");
await bumpOrder.waitFor({ state: "visible", timeout: 5000 });
expect(bumpOrder).toBeVisible();

await page.screenshot({ path: "screenshots/test-failure.png" });

// Sort by bump order.
await bumpOrder.click();

await page.screenshot();

// Expect the markets to be in bump order.
marketGridItems = page.locator("#emoji-grid a").getByTitle(/RAT,/, { exact: true });
marketGridItems = page.locator("#emoji-grid a").getByTitle(/RAT,/);
await marketGridItems.first().waitFor({ state: "visible", timeout: 5000 });
await page.screenshot({ path: "screenshots/test-failure-2.png" });
expect(marketGridItems.first()).toBeVisible();
expect(marketGridItems).toHaveText(patterns.reverse());

await page.screenshot();
});
19 changes: 10 additions & 9 deletions src/typescript/frontend/tests/e2e/search.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { test, expect } from "@playwright/test";
import { EmojicoinClient } from "../../../sdk/src/client/emojicoin-client";
import { getFundedAccount } from "../../../sdk/src/utils/test/test-accounts";
import { sleep, SYMBOL_EMOJI_DATA } from "../../../sdk/src";
import { SYMBOL_EMOJI_DATA } from "../../../sdk/src";

test("check search results", async ({ page }) => {
const user = getFundedAccount("666");
const cat = SYMBOL_EMOJI_DATA.byName("cat")!.emoji;
const symbols = [cat, cat];

const client = new EmojicoinClient();
await client.register(user, symbols).then((res) => res.handle);
// Register the market if it doesn't exist- it should only exist if the test is retried.
const exists = await client.view.marketExists(symbols);
if (!exists) {
await client.register(user, symbols);
}

await page.goto("/home");

Expand All @@ -28,15 +32,12 @@ test("check search results", async ({ page }) => {
await emojiSearch.fill("cat");

// Expect the "cat" emoji to be visible in the search results.
let emojiSearchCatButton = picker.getByLabel(cat).first();
// Note: we must use `getByRole` with 'button' because this element is in the picker shadow DOM.
let emojiSearchCatButton = picker.getByRole("button", { name: cat, exact: true });
expect(emojiSearchCatButton).toBeVisible();

// Search for the cat,cat market.
await emojiSearchCatButton.click({ force: true });

emojiSearchCatButton = picker.getByLabel(cat).first();
expect(emojiSearchCatButton).toBeVisible();
await emojiSearchCatButton.click({ force: true });
// Search for the cat,cat market by clicking twice.
await emojiSearchCatButton.click({ force: true, clickCount: 2 });

// Click on the cat,cat market.
const marketCard = page.getByText("cat,cat", { exact: true });
Expand Down
3 changes: 2 additions & 1 deletion src/typescript/frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"./src/**/*.ts",
"./src/**/*.tsx",
".next/types/**/*.ts",
"./dist/types/**/*.ts"
"./dist/types/**/*.ts",
"playwright.config.ts"
]
}
4 changes: 2 additions & 2 deletions src/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"dev:debug-verbose": "FETCH_DEBUG_VERBOSE=true pnpm load-env -- pnpm run dev",
"docker:prune": "../docker/utils/prune.sh --reset-localnet --yes",
"docker:restart": "pnpm run docker:prune && pnpm run docker:up",
"docker:up": "docker compose -f ../docker/compose.local.yaml --env-file ../docker/compose.local.yaml up -d",
"docker:up": "docker compose -f ../docker/compose.local.yaml --env-file ../docker/example.local.env up -d",
"format": "turbo run format -- --write",
"format:check": "turbo run format -- --check",
"lint": "turbo run lint",
Expand All @@ -39,7 +39,7 @@
"submodule": "turbo run submodule",
"test": "pnpm run load-env:test -- turbo run test --force",
"test:debug": "FETCH_DEBUG=true pnpm run load-env:test -- turbo run test --force",
"test:frontend": "pnpm run load-env:test -- turbo run test --filter @econia-labs/emojicoin-frontend --log-prefix none",
"test:frontend": " pnpm run load-env:test -- turbo run test --filter @econia-labs/emojicoin-frontend --log-prefix none",
"test:frontend:e2e": "pnpm run load-env:test -- turbo run test:e2e --filter @econia-labs/emojicoin-frontend --log-prefix none",
"test:sdk": "pnpm run load-env:test -- turbo run test --filter @econia-labs/emojicoin-sdk --log-prefix none",
"test:sdk:e2e": "pnpm run load-env:unit-test -- turbo run test:e2e --filter @econia-labs/emojicoin-sdk --force --log-prefix none --log-order grouped",
Expand Down
34 changes: 31 additions & 3 deletions src/typescript/sdk/src/client/emojicoin-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type WaitForTransactionOptions,
} from "@aptos-labs/ts-sdk";
import { type ChatEmoji, type SymbolEmoji } from "../emoji_data/types";
import { getEvents } from "../emojicoin_dot_fun";
import { EmojicoinDotFun, getEvents } from "../emojicoin_dot_fun";
import {
Chat,
ProvideLiquidity,
Expand All @@ -28,6 +28,7 @@ import { DEFAULT_REGISTER_MARKET_GAS_OPTIONS, INTEGRATOR_ADDRESS } from "../cons
import { waitFor } from "../utils";
import { postgrest } from "../indexer-v2/queries";
import { TableName } from "../indexer-v2/types/json-types";
import { type AnyNumberString } from "../types";

const { expect, Expect } = customExpect;

Expand Down Expand Up @@ -100,18 +101,32 @@ export class EmojicoinClient {
remove: this.removeLiquidity.bind(this),
};

public utils = {
public utils: {
emojisToHexStrings: typeof EmojicoinClient.prototype.emojisToHexStrings;
emojisToHexSymbol: typeof EmojicoinClient.prototype.emojisToHexSymbol;
getEmojicoinInfo: typeof EmojicoinClient.prototype.getEmojicoinInfo;
getTransactionEventData: typeof EmojicoinClient.prototype.getTransactionEventData;
} = {
emojisToHexStrings: this.emojisToHexStrings.bind(this),
emojisToHexSymbol: this.emojisToHexSymbol.bind(this),
getEmojicoinInfo: this.getEmojicoinInfo.bind(this),
getTransactionEventData: this.getTransactionEventData.bind(this),
};

public rewards = {
public rewards: {
buy: typeof EmojicoinClient.prototype.buyWithRewards;
sell: typeof EmojicoinClient.prototype.sellWithRewards;
} = {
buy: this.buyWithRewards.bind(this),
sell: this.sellWithRewards.bind(this),
};

public view: {
marketExists: typeof EmojicoinClient.prototype.isMarketRegisteredView;
} = {
marketExists: this.isMarketRegisteredView.bind(this),
};

private integrator: AccountAddress;

private integratorFeeRateBPs: number;
Expand Down Expand Up @@ -228,6 +243,19 @@ export class EmojicoinClient {
);
}

private async isMarketRegisteredView(
symbolEmojis: SymbolEmoji[],
ledgerVersion?: AnyNumberString
) {
const { marketAddress } = this.getEmojicoinInfo(symbolEmojis);
const res = await EmojicoinDotFun.MarketMetadataByMarketAddress.view({
aptos: this.aptos,
marketAddress,
...(ledgerVersion ? { options: { ledgerVersion: BigInt(ledgerVersion) } } : {}),
});
return typeof res.vec.pop() !== "undefined";
}

private async swap(
swapper: Account,
symbolEmojis: SymbolEmoji[],
Expand Down
10 changes: 10 additions & 0 deletions src/typescript/sdk/src/utils/test/docker/docker-test-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { EMOJICOIN_INDEXER_URL } from "../../../server/env";
import { TableName } from "../../../indexer-v2/types/json-types";
import { readFileSync, writeFileSync } from "node:fs";
import { execSync } from "node:child_process";

const LOCAL_COMPOSE_PATH = path.join(getGitRoot(), "src/docker", "compose.local.yaml");
const LOCAL_ENV_PATH = path.join(getGitRoot(), "src/docker", "example.local.env");
Expand Down Expand Up @@ -116,6 +117,15 @@ export class DockerTestHarness {
await DockerTestHarness.remove();

const command = "docker";

// Always build the frontend container if we're using it.
if (frontend) {
execSync(
`docker compose -f ${LOCAL_COMPOSE_PATH} --env-file ${LOCAL_ENV_PATH} build frontend`,
{ stdio: "inherit" }
);
}

const args = [
"compose",
"-f",
Expand Down
10 changes: 3 additions & 7 deletions src/typescript/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@
"cache": false,
"persistent": true
},
"e2e:frontend": {
"cache": false,
"outputs": []
},
"format": {
"outputs": []
},
Expand Down Expand Up @@ -64,15 +60,15 @@
"cache": false,
"outputs": []
},
"test:parallel": {
"test:e2e": {
"cache": false,
"outputs": []
},
"test:sequential": {
"test:parallel": {
"cache": false,
"outputs": []
},
"unit-test": {
"test:sequential": {
"cache": false,
"outputs": []
}
Expand Down
Loading