Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ templates/eject/rust/src/satellite/satellite_extension.did
/blob-report/
/playwright/.cache/
/playwright/.auth/

.snapshots
19 changes: 0 additions & 19 deletions e2e/create-website.spec.ts

This file was deleted.

3 changes: 3 additions & 0 deletions e2e/page-objects/_page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export abstract class TestPage {
abstract close(): Promise<void>;
}
183 changes: 183 additions & 0 deletions e2e/page-objects/cli.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import {assertNonNullish, notEmptyString} from '@dfinity/utils';
import type {PrincipalText} from '@dfinity/zod-schemas';
import {execute, spawn} from '@junobuild/cli-tools';
import {readdirSync, statSync} from 'node:fs';
import {readFile, writeFile} from 'node:fs/promises';
import {join} from 'node:path';
import {TestPage} from './_page';

const DEV = (process.env.NODE_ENV ?? 'production') === 'development';

const JUNO_CONFIG = join(process.cwd(), 'juno.config.ts');

const JUNO_TEST_ARGS = ['--mode', 'development', '--headless'];

const {command: JUNO_CMD, args: JUNO_CDM_ARGS} = DEV
? {command: 'node', args: ['dist/index.js']}
: {command: 'juno', args: []};

const buildArgs = (args: string[]): string[] => [...JUNO_CDM_ARGS, ...args, ...JUNO_TEST_ARGS];

export interface CliPageParams {
satelliteId: PrincipalText;
}

export class CliPage extends TestPage {
readonly #satelliteId: PrincipalText;

private constructor({satelliteId}: CliPageParams) {
super();

this.#satelliteId = satelliteId;
}

static async initWithEmulatorLogin(params: CliPageParams): Promise<CliPage> {
const cliPage = new CliPage(params);

await cliPage.initConfig();

await cliPage.loginWithEmulator();

await cliPage.applyConfig();

return cliPage;
}

protected async initConfig(): Promise<void> {
let content = await readFile(JUNO_CONFIG, 'utf-8');
content = content.replace('<DEV_SATELLITE_ID>', this.#satelliteId);
await writeFile(JUNO_CONFIG, content, 'utf-8');
}

private async revertConfig(): Promise<void> {
let content = await readFile(JUNO_CONFIG, 'utf-8');
content = content.replace(this.#satelliteId, '<DEV_SATELLITE_ID>');
await writeFile(JUNO_CONFIG, content, 'utf-8');
}

protected async loginWithEmulator(): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['login', '--emulator'])
});
}

async applyConfig(): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['config', 'apply', '--force'])
});
}

private async logout(): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['logout'])
});
}

async clearHosting(): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['hosting', 'clear'])
});
}

async createSnapshot({
target
}: {
target: 'satellite' | 'orbiter' | 'mission-control';
}): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['snapshot', 'create', '--target', target])
});
}

async restoreSnapshot({
target
}: {
target: 'satellite' | 'orbiter' | 'mission-control';
}): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['snapshot', 'restore', '--target', target])
});
}

async deleteSnapshot({
target
}: {
target: 'satellite' | 'orbiter' | 'mission-control';
}): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['snapshot', 'delete', '--target', target])
});
}

async downloadSnapshot({
target
}: {
target: 'satellite' | 'orbiter' | 'mission-control';
}): Promise<{snapshotFolder: string}> {
await execute({
command: JUNO_CMD,
args: buildArgs(['snapshot', 'download', '--target', target])
});

// Retrieve where the snapshot was created
const snapshotsFolder = join(process.cwd(), '.snapshots');
const [snapshotFolder] = readdirSync(snapshotsFolder, {withFileTypes: true})
.filter((d) => d.isDirectory())
.map(({name}) => {
const path = join(snapshotsFolder, name);
const {birthtimeMs: time} = statSync(path);
return {path, time};
})
.sort((a, b) => b.time - a.time);

assertNonNullish(snapshotFolder);

return {snapshotFolder: snapshotFolder.path};
}

async uploadSnapshot({
target,
folder
}: {
target: 'satellite' | 'orbiter' | 'mission-control';
folder: string;
}): Promise<void> {
await execute({
command: JUNO_CMD,
args: buildArgs(['snapshot', 'upload', '--target', target, '--dir', folder])
});
}

async listSnapshot({
target
}: {
target: 'satellite' | 'orbiter' | 'mission-control';
}): Promise<{snapshotId: string | undefined}> {
let output = '';

await spawn({
command: JUNO_CMD,
args: buildArgs(['snapshot', 'list', '--target', target]),
stdout: (o) => (output += o),
silentErrors: true
});

const [_, snapshotId] = output.split('Snapshot found:');
return {snapshotId: notEmptyString(snapshotId) ? snapshotId.trim() : undefined};
}

/**
* @override
*/
async close(): Promise<void> {
await this.revertConfig();
await this.logout();
}
}
28 changes: 23 additions & 5 deletions e2e/page-objects/console.page.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {InternetIdentityPage} from '@dfinity/internet-identity-playwright';
import {expect} from '@playwright/test';
import type {Page} from 'playwright-core';
import {testIds} from '../constants/test-ids.constants';
import {IdentityPage, type IdentityPageParams} from './identity.page';
import {SatellitePage} from './satellite.page';

export class ConsolePage extends IdentityPage {
readonly #consoleIIPage: InternetIdentityPage;

constructor(params: IdentityPageParams) {
private constructor(params: IdentityPageParams) {
super(params);

this.#consoleIIPage = new InternetIdentityPage({
Expand All @@ -17,6 +17,18 @@ export class ConsolePage extends IdentityPage {
});
}

static async initWithSignIn(params: IdentityPageParams): Promise<ConsolePage> {
const consolePage = new ConsolePage(params);

await consolePage.waitReady();

await consolePage.goto();

await consolePage.signIn();

return consolePage;
}

async goto(): Promise<void> {
await this.page.goto('/');
}
Expand Down Expand Up @@ -53,7 +65,9 @@ export class ConsolePage extends IdentityPage {
await this.page.getByTestId(testIds.createSatellite.continue).click();
}

async visitSatellite(): Promise<Page> {
async visitSatelliteSite(
{title}: {title: string} = {title: 'Juno / Satellite'}
): Promise<SatellitePage> {
await expect(this.page.getByTestId(testIds.satelliteOverview.visit)).toBeVisible();

const satellitePagePromise = this.context.waitForEvent('page');
Expand All @@ -62,8 +76,12 @@ export class ConsolePage extends IdentityPage {

const satellitePage = await satellitePagePromise;

await expect(satellitePage).toHaveTitle('Juno / Satellite');
await expect(satellitePage).toHaveTitle(title);

return satellitePage;
return new SatellitePage({
page: satellitePage,
browser: this.browser,
context: this.context
});
}
}
8 changes: 7 additions & 1 deletion e2e/page-objects/identity.page.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import type {Browser, BrowserContext, Page} from '@playwright/test';
import {TestPage} from './_page';

export interface IdentityPageParams {
page: Page;
context: BrowserContext;
browser: Browser;
}

export abstract class IdentityPage {
export abstract class IdentityPage extends TestPage {
protected identity: number | undefined;

protected readonly page: Page;
protected readonly context: BrowserContext;
protected readonly browser: Browser;

protected constructor({page, context, browser}: IdentityPageParams) {
super();

this.page = page;
this.context = context;
this.browser = browser;
}

/**
* @override
*/
async close(): Promise<void> {
await this.page.close();
}
Expand Down
23 changes: 23 additions & 0 deletions e2e/page-objects/satellite.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {expect} from '@playwright/test';
import {IdentityPage} from './identity.page';

export class SatellitePage extends IdentityPage {
async reload({title}: {title: string} = {title: 'Juno / Satellite'}): Promise<void> {
await expect
.poll(
async () => {
await this.page.reload({waitUntil: 'load'});
return await this.page.title();
},
{
timeout: 30000,
intervals: [1_000, 2_000, 10_000]
}
)
.toBe(title);
}

async assertScreenshot(): Promise<void> {
await expect(this.page).toHaveScreenshot({fullPage: true});
}
}
54 changes: 54 additions & 0 deletions e2e/snapshots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {testWithII} from '@dfinity/internet-identity-playwright';
import {expect} from '@playwright/test';
import {initTestSuite} from './utils/init.utils';

const getTestPages = initTestSuite();

const SNAPSHOT_TARGET = {target: 'satellite' as const};

testWithII('should create and restore a snapshot', async () => {
const {consolePage, cliPage} = getTestPages();

await cliPage.createSnapshot(SNAPSHOT_TARGET);

await cliPage.clearHosting();

const satellitePage = await consolePage.visitSatelliteSite({
title: 'Internet Computer - Error: response verification error'
});
await satellitePage.assertScreenshot();

await cliPage.restoreSnapshot(SNAPSHOT_TARGET);

await satellitePage.reload();
await satellitePage.assertScreenshot();
});

testWithII('should create, download, delete, upload and restore a snapshot', async () => {
testWithII.slow();

const {consolePage, cliPage} = getTestPages();

await cliPage.createSnapshot(SNAPSHOT_TARGET);

const {snapshotFolder} = await cliPage.downloadSnapshot(SNAPSHOT_TARGET);

await cliPage.deleteSnapshot(SNAPSHOT_TARGET);

const {snapshotId} = await cliPage.listSnapshot(SNAPSHOT_TARGET);
expect(snapshotId).toBeUndefined();

await cliPage.clearHosting();

const satellitePage = await consolePage.visitSatelliteSite({
title: 'Internet Computer - Error: response verification error'
});
await satellitePage.assertScreenshot();

await cliPage.uploadSnapshot({...SNAPSHOT_TARGET, folder: snapshotFolder});

await cliPage.restoreSnapshot(SNAPSHOT_TARGET);

await satellitePage.reload();
await satellitePage.assertScreenshot();
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading