Skip to content
Open
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
17 changes: 17 additions & 0 deletions e2e/login.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import test from 'node:test';
import {testWithII} from '../src';

const loginSelector = '#login';
Expand Down Expand Up @@ -45,3 +46,19 @@ testWithII.describe('with selector', () => {
await iiPage.signInWithIdentity({identity, selector: loginSelector});
});
});

testWithII.describe.serial('create identity and manual sign-in', () => {
let createdIdentity: number;

testWithII('create identity and capture it', async ({page, iiPage}) => {
await page.goto('/');
createdIdentity = await iiPage.createNewIdentity();
});

testWithII('sign in using identity from list or fallback manually', async ({page, iiPage}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the title of the test accurate, from the code is seems that we login, logout and then login again.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, the title can be somewhat missleading. Basically the login, logout and login are used to simulate the exact flow. There is an initial login, the "automated" if the Identity is showing in the list, or the fallback if the initial Login happened but the Identity is not showing in the list.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can review the title or add a comment about the flow then?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestion on what to name it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an initial login, the "automated" if the Identity is showing in the list, or the fallback

Reading your feedback again and noticing the mention of or make me think: shouldn't it be two tests actually? Or did you meant and?

await page.goto('/');
await iiPage.manuallySignInWithIdentity({identity: createdIdentity});
await page.locator(logoutSelector).click();
await iiPage.manuallySignInWithIdentity({identity: createdIdentity});
});
});
92 changes: 92 additions & 0 deletions src/page-objects/InternetIdentityPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,96 @@
await iiPage.waitForEvent('close');
expect(iiPage.isClosed()).toBe(true);
};

createNewIdentity = async (params?: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between this new function and signInWithNewIdentity? i.e. Why introduce a new function instead of enhancing the existing one, since they seem to share quite a bit of code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a mixture of what is already there, yes. But I feel like its a very abstract case, so having it as a new function gives clarity and helps for testing purposes. The first PR was basically what you suggest, but then the test didnt check all options, so having it "standalone" just makes it easier to go through all scenarios.

Copy link
Member

@peterpeterparker peterpeterparker Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. I'm not convinced it should be two distinct functions, but I can live with it.

However, in that case, we should improve the naming, as it wasn’t clear to me at first glance which function does what.

Please also add a TODO (and create a follow-up PR once this one is merged) for some post-merge cleanup/refactoring, as the new function duplicates code. This will be especially useful since I’d not like to maintain various chunks of code that reflect the exact same II flow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, aside from iiPage.locator('#displayUserContinue'), this new createNewIdentity function is almost identical to the existing signInWithNewIdentity. So I don't think we need to add it.

If we need different behavior, let's just add an option to the existing signInWithNewIdentity instead.

selector?: string;
captcha?: boolean;
}): Promise<number> => {
const iiPagePromise = this.context.waitForEvent('page');

await this.page.locator(params?.selector ?? '[data-tid=login-button]').click();

const iiPage = await iiPagePromise;
await expect(iiPage).toHaveTitle('Internet Identity');

await iiPage.locator('#registerButton').click();
await iiPage.locator('[data-action=construct-identity]').click();

if (params?.captcha === true) {
await iiPage.locator('input#captchaInput').fill('a', {timeout: 10000});
await iiPage.locator('#confirmRegisterButton').click();
}

const identityText = await iiPage.locator('#userNumber').textContent();
const createdIdentity = parseInt(identityText!);
expect(createdIdentity).not.toBeNull();
return createdIdentity;
}

manuallySignInWithIdentity = async ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between manuallySignInWithIdentity and signInWithIdentity? While they do differ, the naming suggests a similar intent.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming should be better, agreed. Basically as mentioned in the other comment, the manuallySignIn is an extended form that checks for "all possible" ways to login. Since we need a "clean start" where no Identity is "known", we have the possibility to cycle through all available options. This is done inside of manuallySignInWithIdentity

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, I guess same as my previous comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here a few naming alternative:

  • enterIdentityAndSignIn
  • signInWithPromptedIdentity
  • inputIdentityAndSignIn

The first doesn't sound too bad?

Also, the JS Docs is missing, can you please add it as well.

selector,
identity
}: {
selector?: string;
identity: number;
}): Promise<void> => {
const iiPagePromise = this.context.waitForEvent('page');

await this.page.locator(selector ?? '[data-tid=login-button]').click();

const iiPage = await iiPagePromise;
await expect(iiPage).toHaveTitle('Internet Identity');

const existingIdentityLoginLocator = iiPage.locator(`[data-anchor-id="${identity}"]`);
const firstRunManualLoginLocator = iiPage.locator('#loginButton');
const moreOptionsLoginLocator = iiPage.locator('[data-role="more-options"]');

const waitOptions: { state: 'visible'; timeout: number } = { state: 'visible', timeout: 10000 };

type RaceResult = 'existingIdentityLogin' | 'firstRunManualLogin' | 'moreOptionsLogin' | 'none';

async function getRaceResult(

Check warning on line 204 in src/page-objects/InternetIdentityPage.ts

View workflow job for this annotation

GitHub Actions / lint

Functions with more than one parameter should accept an object and use destructuring
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: for consistency with our codebase I would use a const getRaceResult = () ...

locator: { waitFor: (options: any) => Promise<any> },

Check failure on line 205 in src/page-objects/InternetIdentityPage.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check failure on line 205 in src/page-objects/InternetIdentityPage.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter disallow any

successResult: RaceResult
): Promise<RaceResult> {
try {
await locator.waitFor(waitOptions);
return successResult;
} catch {
return 'none';
}
}

const identityPromise = getRaceResult(existingIdentityLoginLocator, 'existingIdentityLogin');
const firstTimePromise = getRaceResult(firstRunManualLoginLocator, 'firstRunManualLogin');
const fallbackPromise = getRaceResult(moreOptionsLoginLocator, 'moreOptionsLogin');

const result: RaceResult = await Promise.race([
identityPromise,
fallbackPromise,
firstTimePromise,
]);

switch(result) {
case 'existingIdentityLogin':
await existingIdentityLoginLocator.first().click({ force: true });
break;

case 'firstRunManualLogin':
case 'moreOptionsLogin': {
const locator =
result === 'firstRunManualLogin' ? firstRunManualLoginLocator : moreOptionsLoginLocator;
await locator.click({ force: true });
await iiPage.fill('input[data-role="anchor-input"]', identity.toString());
await iiPage.locator('[data-action="continue"]').click();
break;
}
default:
throw new Error(
'No locator found for Buttons: existingIdentity, firstRunManualLogin or moreOptionsLogin'
);
}
await iiPage.waitForEvent('close');
expect(iiPage.isClosed()).toBe(true);
};
}
Loading