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: 1 addition & 1 deletion .github/workflows/e2e-snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Run tests and update Snapshots
run: |
juno dev start --headless &
./scripts/e2e-starter e2e:ci:snapshots
./scripts/e2e e2e:ci:snapshots

- name: Commit Playwright updated snapshots
uses: EndBug/add-and-commit@v9
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Run tests
run: |
juno dev start --headless &
./scripts/e2e-starter e2e:ci
./scripts/e2e e2e:ci

- name: Upload Playwright report on failure
uses: actions/upload-artifact@v4
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:
juno-satellite:
image: junobuild/satellite:latest
ports:
- 4943:5987
- 5987:5987
- 5999:5999
volumes:
- juno_satellite:/juno/.juno
Expand Down
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
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
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
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
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
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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/data/dog.jpg
89 changes: 89 additions & 0 deletions e2e/example.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {testWithII} from '@dfinity/internet-identity-playwright';
import {initTestSuite} from './utils/init.utils';

const getExamplePage = initTestSuite();

testWithII('should sign-in', async () => {
const examplePage = getExamplePage();

await examplePage.assertSignedIn();
});

testWithII('should add an entry', async () => {
const examplePage = getExamplePage();

await examplePage.addEntry('My notes.');
});

testWithII('should add an entry with file', async () => {
const examplePage = getExamplePage();

await examplePage.addEntryWithFile({
text: 'My file.',
filePath: 'e2e/data/dog.jpg'
});

await examplePage.assertUploadedImage();
});

const lastEntryText = 'My last note.';

testWithII('should add another entry', async () => {
const examplePage = getExamplePage();

await examplePage.addEntry(lastEntryText);
});

testWithII('should delete entry', async () => {
const examplePage = getExamplePage();

await examplePage.deleteLastEntry();
});

testWithII('should sign-out', async () => {
const examplePage = getExamplePage();

await examplePage.signOut();

await examplePage.assertSignedOut();
});

// TODO: testWithII does not seem to support setting dark or light mode so for now we just use screenshot of default mode

testWithII('match login screenshot', async () => {
const examplePage = getExamplePage();

await examplePage.assertSignedOut();

await examplePage.assertScreenshot({mode: 'current', name: 'login'});
});

testWithII('match logged in screenshot', async () => {
const examplePage = getExamplePage();

await examplePage.signInWithIdentity();

await examplePage.assertSignedIn();

await examplePage.assertScreenshot({mode: 'current', name: 'logged-in'});
});

testWithII('match modal screenshot', async () => {
const examplePage = getExamplePage();

await examplePage.openAddEntry();

await examplePage.assertScreenshot({mode: 'current', name: 'modal'});

await examplePage.closeAddEntryModal();
});

testWithII('match logout screenshot', async () => {
const examplePage = getExamplePage();

await examplePage.signOut();

await examplePage.assertSignedOut();

await examplePage.assertScreenshot({mode: 'current', name: 'logout'});
});
152 changes: 152 additions & 0 deletions e2e/page-objects/example.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import {InternetIdentityPage} from '@dfinity/internet-identity-playwright';
import {assertNonNullish} from '@dfinity/utils';
import {expect} from '@playwright/test';
import {IdentityPage, IdentityPageParams} from './identity.page';

export class ExamplePage extends IdentityPage {
#partyIIPage: InternetIdentityPage;

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

this.#partyIIPage = new InternetIdentityPage({
page: this.page,
context: this.context,
browser: this.browser
});
}

/**
* @override
*/
async signIn(): Promise<void> {
this.identity = await this.#partyIIPage.signInWithNewIdentity({
selector: 'button:has-text("Sign in")'
});
}

async signInWithIdentity(): Promise<void> {
assertNonNullish(this.identity);

await this.#partyIIPage.signInWithIdentity({
identity: this.identity,
selector: 'button:has-text("Sign in")'
});
}

/**
* @override
*/
async signOut(): Promise<void> {
const button = this.page.locator('button', {hasText: 'Logout'});
await button.click();
}

async assertSignedIn(): Promise<void> {
const button = this.page.locator('button', {hasText: 'Logout'});
await expect(button).toBeVisible();
}

async assertSignedOut(): Promise<void> {
const button = this.page.locator('button', {hasText: 'Sign in'});
await expect(button).toBeVisible();
}

async waitReady(): Promise<void> {
const REPLICA_URL = 'http://127.0.0.1:5987';
const INTERNET_IDENTITY_ID = 'rdmx6-jaaaa-aaaaa-aaadq-cai';

await this.#partyIIPage.waitReady({url: REPLICA_URL, canisterId: INTERNET_IDENTITY_ID});
}

async goto(): Promise<void> {
await this.page.goto('/');
}

async addEntry(text: string): Promise<void> {
const addEntryButton = this.page.locator('button', {hasText: 'Add an entry'});
await expect(addEntryButton).toBeVisible();

await addEntryButton.click();

const textarea = this.page.locator('textarea');
await textarea.fill(text);

const button = this.page.locator('button', {hasText: 'Submit'});
await button.click();

const row = this.page.locator('[role="row"]', {hasText: text});
await expect(row).toBeVisible();
}

async addEntryWithFile({text, filePath}: {text: string; filePath: string}): Promise<void> {
const addEntryButton = this.page.locator('button', {hasText: 'Add an entry'});
await expect(addEntryButton).toBeVisible();

await addEntryButton.click();

const textarea = this.page.locator('textarea');
await textarea.fill(text);

const fileInput = this.page.locator('input[type="file"]');
await fileInput.setInputFiles(filePath);

const button = this.page.locator('button', {hasText: 'Submit'});
await button.click();

const row = this.page.locator('[role="row"]', {hasText: text});
await expect(row).toBeVisible({timeout: 60_000});
}

async assertUploadedImage(): Promise<void> {
const [imgPage] = await Promise.all([
this.page.context().waitForEvent('page'),
this.page.locator('a[aria-label="Open data"]').click()
]);

await imgPage.waitForLoadState('load');

await expect(imgPage).toHaveScreenshot('uploaded-image.png', {
maxDiffPixelRatio: 0.1
});

await imgPage.close();
}

async deleteLastEntry(): Promise<void> {
const buttons = this.page.locator('button[aria-label="Delete entry"]');
await buttons.last().click();

await expect(this.page.locator('[role="row"]', {hasText: 'text'})).toHaveCount(0);
}

async assertScreenshot({
mode,
name
}: {
mode: 'light' | 'dark' | 'current';
name: string;
}): Promise<void> {
await expect(this.page).toHaveScreenshot(`${name}-${mode}-mode.png`, {
fullPage: true,
maxDiffPixelRatio: 0.1
});
}

async openAddEntry(): Promise<void> {
const addEntryButton = this.page.locator('button', {hasText: 'Add an entry'});
await expect(addEntryButton).toBeVisible();

await addEntryButton.click();

const textarea = this.page.locator('textarea');
await expect(textarea).toBeVisible();
}

async closeAddEntryModal(): Promise<void> {
const closeAddEntryButton = this.page.locator('button', {hasText: 'Close'});
await expect(closeAddEntryButton).toBeVisible();

await closeAddEntryButton.click();
}
}
29 changes: 29 additions & 0 deletions e2e/page-objects/identity.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {Browser, BrowserContext, Page} from '@playwright/test';

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

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

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

protected constructor({page, context, browser}: IdentityPageParams) {
this.page = page;
this.context = context;
this.browser = browser;
}

abstract signIn(): Promise<void>;

abstract signOut(): Promise<void>;

async close(): Promise<void> {
await this.page.close();
}
}
5 changes: 4 additions & 1 deletion e2e/starter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ const TEMPLATE = process.env.TEMPLATE ?? '';

await expect(page.getByText('Welcome to Juno')).toBeVisible();

await expect(page).toHaveScreenshot(`${mode}-mode.png`, {fullPage: true});
await expect(page).toHaveScreenshot(`${mode}-mode.png`, {
fullPage: true,
maxDiffPixelRatio: 0.1
});
});
});
});
Expand Down
35 changes: 35 additions & 0 deletions e2e/utils/init.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {testWithII} from '@dfinity/internet-identity-playwright';
import {ExamplePage} from '../page-objects/example.page';

export const initTestSuite = (): (() => ExamplePage) => {
testWithII.describe.configure({mode: 'serial'});

let examplePage: ExamplePage;

testWithII.beforeAll(async ({playwright}) => {
testWithII.setTimeout(120000);

const browser = await playwright.chromium.launch();

const context = await browser.newContext();
const page = await context.newPage();

examplePage = new ExamplePage({
page,
context,
browser
});

await examplePage.waitReady();

await examplePage.goto();

await examplePage.signIn();
});

testWithII.afterAll(async () => {
await examplePage.close();
});

return (): ExamplePage => examplePage;
};
20 changes: 18 additions & 2 deletions juno.dev.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,24 @@ import {defineDevConfig} from '@junobuild/config';
export default defineDevConfig(() => ({
satellite: {
collections: {
datastore: [],
storage: []
datastore: [
{
collection: 'notes',
read: 'managed',
write: 'managed',
memory: 'stable',
mutablePermissions: true
}
],
storage: [
{
collection: 'images',
read: 'managed',
write: 'managed',
memory: 'stable',
mutablePermissions: true
}
]
}
}
}));
Loading