Skip to content

test(Playwright): Set up Playwright [WPB-17365] #19061

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

Merged
merged 21 commits into from
May 27, 2025

Conversation

iskvortsov
Copy link
Contributor

@iskvortsov iskvortsov commented Apr 30, 2025

EpicWPB-17365 Set up automation for critical flow with Playwright

Description

Refer to the epic: https://wearezeta.atlassian.net/browse/WPB-17365

Checklist

  • mentions the JIRA issue in the PR name (Ex. [WPB-XXXX])
  • PR has been self reviewed by the author;
  • Hard-to-understand areas of the code have been commented;
  • If it is a core feature, unit tests have been added;

@CLAassistant
Copy link

CLAassistant commented Apr 30, 2025

CLA assistant check
All committers have signed the CLA.

@V-Gira V-Gira force-pushed the feat/WPB-17365_implement_playwright_framework branch from 5c3e71d to 9fda508 Compare April 30, 2025 15:06
@V-Gira V-Gira changed the title feat(Playwright): WPB-17365 Initial Playwright set up test(Playwright): Set up Playwright [WPB-17365] Apr 30, 2025
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure which browsers we need, so I've commented our all except for chromium

Copy link
Contributor

Choose a reason for hiding this comment

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

We can decide what browsers we need, maybe we can pick those we support.
Do you think we should configure it using env var?

Choose a reason for hiding this comment

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

If you can configure it to use env var, I'd try for that; then you'd be able to set up tests using different browsers with tagging the ones which cover items important to try in other browsers

@iskvortsov iskvortsov marked this pull request as ready for review May 16, 2025 15:33
@iskvortsov iskvortsov requested review from otto-the-bot and a team as code owners May 16, 2025 15:33

import {getCredentials} from '../utils/credentialsReader';

const onePasswordItemName = 'BackendConnection staging-with-webapp-master';
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like onePasswordItemName is gonna be used a lot in various tests?
It would be great to extract it as reusable const then

Choose a reason for hiding this comment

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

Question: Does this method mean it'll prompt for 1Password interaction multiple times in a test run?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe I can read it one time per test suite run and store it in some data class. Would that be ok?

Choose a reason for hiding this comment

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

Yeah and there's a few options for how to go about that, for example in Android and iOS we are creating an env file using 1Password's template capabilities

Copy link
Contributor

@e-maad e-maad left a comment

Choose a reason for hiding this comment

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

Overall looks good, I really like the usage of page objects and data factories 🚀
Added few concerns.

Comment on lines 31 to 37
const axiosInstance = axios.create({
baseURL: BASE_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move out this initialization?
I would prefer to initialize axiosInstance once for each test session.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean initialize BE client with axiosInstance per each test method? Or did I misunderstand your implications?

Copy link
Contributor

Choose a reason for hiding this comment

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

For each call you are initializing axiosInstance, you don't need a new instance for each call. You can initialize only once and then you could use it everywhere.

Comment on lines 126 to 133
const deleteResponse = await axiosInstance.request({
url: '/self',
method: 'DELETE',
headers: {
Authorization: `Bearer ${user.token}`,
},
data: deletePayload,
});
Copy link
Contributor

@e-maad e-maad May 16, 2025

Choose a reason for hiding this comment

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

Can we create a module that expose api helper functions (get, put .. etc).
Then we don't have to pass the token every time. We can also maintain the user data using singleton class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you elaborate of maintaining the user data using a singleton? I thought of just using ClientUser class instances with this user data. Could be useful if we have more than one user in test case.

set token(value: string) {
this.accessToken = value;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer to have object value factories (TS friendly) for mock data:

interface User {
  email: string;
  password: string;
  firstName: string;
  lastName: string;
  username: string;
  token: string;
}

export const getUser = (user: Partial<User> = {}) => ({
  ...user,
  email: user?.email ?? faker.internet.email({lastName: user?.lastName, provider: 'wire.engineering'}).toLowerCase(),
  password: user?.password ?? faker.internet.password({length: 8, pattern: /[A-Za-z\d!@#$]/}),
  firstName: user?.firstName ?? faker.person.firstName(),
  lastName: user?.lastName ?? faker.person.lastName(),
  username: user?.username ?? `${user?.lastName}${faker.string.alpha({length: 5, casing: 'lower'})}`.toLowerCase(),
  token: user?.token ?? null,
});

const testUser1 = getUser();
const testUser2 = getUser({email: '[email protected]'});

This will be more readable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

+1. Builder or Factories for data like this tend to be a good idea long term.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, initially I have created a constructor for different cases, but I'm not sure it will be used. So I've decided not to overcomplicate things in the beginning but probably it makes sense to have it from the get go.

this.loginErrorText = page.locator('[data-uie-name="error-message"]');
}

async isEmailFieldVisible(): Promise<boolean> {
Copy link
Contributor

Choose a reason for hiding this comment

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

No return type needed.

Suggested change
async isEmailFieldVisible(): Promise<boolean> {
async isEmailFieldVisible() {

Choose a reason for hiding this comment

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

No return type needed.

@e-maad but it does return a boolean and this fits with the page object model design. Why wouldn't a return type be needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to explicitly define return type, in TS it should be auto handled based on the returned value.

// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */

Choose a reason for hiding this comment

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

If you can configure it to use env var, I'd try for that; then you'd be able to set up tests using different browsers with tagging the ones which cover items important to try in other browsers


# Tests

E2E tests (login.spec.ts and credentialsReader.spec.ts) can be found inside [test folder](/test/e2e_tests/). The folder contains [page objects](/test/e2e_tests/pages), [backend classes](/test/e2e_tests/backend), and [credentialsReader.ts](/test//e2e_tests/utils/credentialsReader.ts) for access 1Password credentials.

Choose a reason for hiding this comment

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

Small item: Given the e2e tests are going to grow beyond those two, should their names be removed from this item?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I've just tried to explain what was done in this PR. I'll rewrite it better! Thx for the input


import {getCredentials} from '../utils/credentialsReader';

const onePasswordItemName = 'BackendConnection staging-with-webapp-master';

Choose a reason for hiding this comment

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

Question: Does this method mean it'll prompt for 1Password interaction multiple times in a test run?

throw new Error('zuid cookie not found in register response');
}

// 2. Get activation code

Choose a reason for hiding this comment

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

Getting activation/verification code is something which is gonna be needed separately once we get to automating 2FA scenarios again. While not necessary for this PR, consider extracting this part

set token(value: string) {
this.accessToken = value;
}
}

Choose a reason for hiding this comment

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

+1. Builder or Factories for data like this tend to be a good idea long term.

this.loginErrorText = page.locator('[data-uie-name="error-message"]');
}

async isEmailFieldVisible(): Promise<boolean> {

Choose a reason for hiding this comment

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

No return type needed.

@e-maad but it does return a boolean and this fits with the page object model design. Why wouldn't a return type be needed?

Copy link

@araneforseti araneforseti left a comment

Choose a reason for hiding this comment

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

lgtm - the new changes look very nice!

},
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Great work 👏
For the future use I would be prefer to not put all the api calls in the single class.
For example:

export class UserRepository extends Backend {
 ...
}

export class AuthRepository extends Backend {
 ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that's the plan! Thank you :)

Copy link
Contributor

@e-maad e-maad left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

...user,
email: user.email ?? faker.internet.email({lastName, provider: 'wire.engineering'}).toLowerCase(),
password: user.password ?? faker.internet.password({length: 8, pattern: /[A-Za-z\d!@#$]/}),
firstName,
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for a variable.

Suggested change
firstName,
firstName: user.firstName ?? faker.person.firstName(),

Copy link

@iskvortsov iskvortsov merged commit 15e827e into dev May 27, 2025
13 of 14 checks passed
@iskvortsov iskvortsov deleted the feat/WPB-17365_implement_playwright_framework branch May 27, 2025 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants