diff --git a/e2e/fixtures/index.html b/e2e/fixtures/index.html new file mode 100644 index 00000000..66acd60c --- /dev/null +++ b/e2e/fixtures/index.html @@ -0,0 +1,10 @@ + + + + + Hello World + + +

Hello World

+ + diff --git a/e2e/page-objects/cli.page.ts b/e2e/page-objects/cli.page.ts index 13cb4957..4ce962db 100644 --- a/e2e/page-objects/cli.page.ts +++ b/e2e/page-objects/cli.page.ts @@ -1,8 +1,8 @@ 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 {statSync} from 'node:fs'; +import {readdir, readFile, writeFile} from 'node:fs/promises'; import {join} from 'node:path'; import {TestPage} from './_page'; @@ -23,7 +23,7 @@ export interface CliPageParams { } export class CliPage extends TestPage { - readonly #satelliteId: PrincipalText; + #satelliteId: PrincipalText; private constructor({satelliteId}: CliPageParams) { super(); @@ -55,6 +55,12 @@ export class CliPage extends TestPage { await writeFile(JUNO_CONFIG, content, 'utf-8'); } + async toggleSatelliteId({satelliteId}: {satelliteId: PrincipalText}): Promise { + await this.revertConfig(); + this.#satelliteId = satelliteId; + await this.initConfig(); + } + protected async loginWithEmulator(): Promise { await execute({ command: JUNO_CMD, @@ -83,6 +89,13 @@ export class CliPage extends TestPage { }); } + async deployHosting({clear}: {clear: boolean}): Promise { + await execute({ + command: JUNO_CMD, + args: buildArgs(['hosting', 'deploy', ...(clear ? ['--clear'] : [])]) + }); + } + async createSnapshot({ target }: { @@ -126,9 +139,16 @@ export class CliPage extends TestPage { args: buildArgs(['snapshot', 'download', '--target', target]) }); - // Retrieve where the snapshot was created + return await this.getSnapshotFsFolder(); + } + + // Retrieve where the snapshot was created + async getSnapshotFsFolder(): Promise<{snapshotFolder: string}> { const snapshotsFolder = join(process.cwd(), '.snapshots'); - const [snapshotFolder] = readdirSync(snapshotsFolder, {withFileTypes: true}) + + const folders = await readdir(snapshotsFolder, {withFileTypes: true}); + + const [snapshotFolder] = folders .filter((d) => d.isDirectory()) .map(({name}) => { const path = join(snapshotsFolder, name); @@ -173,6 +193,23 @@ export class CliPage extends TestPage { return {snapshotId: notEmptyString(snapshotId) ? snapshotId.trim() : undefined}; } + async whoami(): Promise<{accessKey: string}> { + let output = ''; + + await spawn({ + command: JUNO_CMD, + args: buildArgs(['whoami']), + stdout: (o) => (output += o), + silentErrors: true + }); + + const [_, __, ___, text] = output.split(' '); + const [value] = text.split('\n'); + const accessKey = value.replace('\x1B[32m', '').replace('\x1B[39m', ''); + + return {accessKey: accessKey.trim()}; + } + /** * @override */ diff --git a/e2e/page-objects/console.page.ts b/e2e/page-objects/console.page.ts index 326abf1f..bd077260 100644 --- a/e2e/page-objects/console.page.ts +++ b/e2e/page-objects/console.page.ts @@ -1,6 +1,6 @@ import {InternetIdentityPage} from '@dfinity/internet-identity-playwright'; import {notEmptyString} from '@dfinity/utils'; -import {PrincipalTextSchema} from '@dfinity/zod-schemas'; +import {PrincipalText, PrincipalTextSchema} from '@dfinity/zod-schemas'; import {expect} from '@playwright/test'; import {testIds} from '../constants/test-ids.constants'; import {IdentityPage, type IdentityPageParams} from './identity.page'; @@ -31,8 +31,8 @@ export class ConsolePage extends IdentityPage { return consolePage; } - async goto(): Promise { - await this.page.goto('/'); + async goto({path}: {path: string} = {path: '/'}): Promise { + await this.page.goto(path); } async signIn(): Promise { @@ -49,11 +49,15 @@ export class ConsolePage extends IdentityPage { } async createSatellite({kind}: {kind: 'website' | 'application'}): Promise { - await expect(this.page.getByTestId(testIds.createSatellite.launch)).toBeVisible(); + await expect(this.page.getByTestId(testIds.createSatellite.launch)).toBeVisible({ + timeout: 20000 + }); await this.page.getByTestId(testIds.createSatellite.launch).click(); - await expect(this.page.getByTestId(testIds.createSatellite.create)).toBeVisible(); + await expect(this.page.getByTestId(testIds.createSatellite.create)).toBeVisible({ + timeout: 15000 + }); await this.page.getByTestId(testIds.createSatellite.input).fill('Test'); await this.page.getByTestId(testIds.createSatellite[kind]).click(); @@ -70,7 +74,9 @@ export class ConsolePage extends IdentityPage { async visitSatelliteSite( {title}: {title: string} = {title: 'Juno / Satellite'} ): Promise { - await expect(this.page.getByTestId(testIds.satelliteOverview.visit)).toBeVisible(); + await expect(this.page.getByTestId(testIds.satelliteOverview.visit)).toBeVisible({ + timeout: 20000 + }); const satellitePagePromise = this.context.waitForEvent('page'); @@ -87,6 +93,18 @@ export class ConsolePage extends IdentityPage { }); } + async getICP(): Promise { + await expect(this.page.getByTestId(testIds.navbar.openWallet)).toBeVisible(); + + await this.page.getByTestId(testIds.navbar.openWallet).click(); + + await expect(this.page.getByTestId(testIds.navbar.getIcp)).toBeVisible(); + + await this.page.getByTestId(testIds.navbar.getIcp).click(); + + await expect(this.page.getByText('55.0001')).toBeVisible({timeout: 65000}); + } + async copySatelliteID(): Promise { await expect(this.page.getByTestId(testIds.satelliteOverview.copySatelliteId)).toBeVisible(); @@ -99,4 +117,34 @@ export class ConsolePage extends IdentityPage { return satelliteId; } + + async addSatelliteAdminAccessKey({ + satelliteId, + accessKey + }: { + satelliteId: PrincipalText; + accessKey: string; + }): Promise { + await this.goto({path: `/satellite/?s=${satelliteId}&tab=setup`}); + + const btnLocator = this.page.locator('button', {hasText: 'Add an access key'}); + await expect(btnLocator).toBeVisible({timeout: 10000}); + await btnLocator.click(); + + const form = this.page.locator('form'); + + await form.getByRole('radio', {name: /enter one manually/i}).check(); + + const keyField = form.getByLabel('Access Key ID'); + await expect(keyField).toBeEnabled(); + await keyField.fill(accessKey); + + await form.locator('select[name="scope"]').selectOption('admin'); + + const submitLocator = form.getByRole('button', {name: /^submit$/i}); + await expect(submitLocator).toBeEnabled(); + await submitLocator.click(); + + await expect(this.page.getByText('Access Key Added')).toBeVisible({timeout: 10000}); + } } diff --git a/e2e/snapshots.spec.ts b/e2e/snapshots.spec.ts index 04cd7517..84126a70 100644 --- a/e2e/snapshots.spec.ts +++ b/e2e/snapshots.spec.ts @@ -25,7 +25,7 @@ testWithII('should create and restore a snapshot', async () => { }); testWithII('should create, download, delete, upload and restore a snapshot', async () => { - testWithII.slow(); + testWithII.setTimeout(120_000); const {consolePage, cliPage} = getTestPages(); @@ -52,3 +52,44 @@ testWithII('should create, download, delete, upload and restore a snapshot', asy await satellitePage.reload(); await satellitePage.assertScreenshot(); }); + +testWithII( + 'should create, download, delete, upload and restore a snapshot to another satellite', + async () => { + testWithII.setTimeout(120_000); + + const {consolePage, cliPage} = getTestPages(); + + await consolePage.getICP(); + + await consolePage.goto(); + + await consolePage.createSatellite({kind: 'application'}); + + const satelliteId = await consolePage.copySatelliteID(); + + await cliPage.toggleSatelliteId({satelliteId}); + + const {accessKey} = await cliPage.whoami(); + + await consolePage.addSatelliteAdminAccessKey({accessKey, satelliteId}); + + await cliPage.deployHosting({clear: true}); + + await consolePage.goto({path: `/satellite/?s=${satelliteId}`}); + + const satellitePage = await consolePage.visitSatelliteSite({ + title: 'Hello World' + }); + await satellitePage.assertScreenshot(); + + const {snapshotFolder} = await cliPage.getSnapshotFsFolder(); + + await cliPage.uploadSnapshot({...SNAPSHOT_TARGET, folder: snapshotFolder}); + + await cliPage.restoreSnapshot(SNAPSHOT_TARGET); + + await satellitePage.reload(); + await satellitePage.assertScreenshot(); + } +); diff --git a/e2e/snapshots/snapshots.spec.ts-snapshots/should-create-download-delete-upload-and-restore-a-snapshot-to-another-satellite-1-chromium-linux.png b/e2e/snapshots/snapshots.spec.ts-snapshots/should-create-download-delete-upload-and-restore-a-snapshot-to-another-satellite-1-chromium-linux.png new file mode 100644 index 00000000..4fbe70d0 Binary files /dev/null and b/e2e/snapshots/snapshots.spec.ts-snapshots/should-create-download-delete-upload-and-restore-a-snapshot-to-another-satellite-1-chromium-linux.png differ diff --git a/e2e/snapshots/snapshots.spec.ts-snapshots/should-create-download-delete-upload-and-restore-a-snapshot-to-another-satellite-2-chromium-linux.png b/e2e/snapshots/snapshots.spec.ts-snapshots/should-create-download-delete-upload-and-restore-a-snapshot-to-another-satellite-2-chromium-linux.png new file mode 100644 index 00000000..7b6610c0 Binary files /dev/null and b/e2e/snapshots/snapshots.spec.ts-snapshots/should-create-download-delete-upload-and-restore-a-snapshot-to-another-satellite-2-chromium-linux.png differ diff --git a/juno.config.ts b/juno.config.ts index 90cddcb0..caa0213c 100644 --- a/juno.config.ts +++ b/juno.config.ts @@ -6,8 +6,8 @@ export default defineConfig({ development: '', production: '' }, - source: 'build', - predeploy: ['npm run build'], + source: 'e2e/fixtures', + precompress: false, collections: { datastore: [ {