Skip to content

Commit

Permalink
Update tests after bug fixes (#846)
Browse files Browse the repository at this point in the history
* test: e2e tests general refactoring and adding custom event waiting

* test: refactor app open logics

* test: fix small details in tests and rebased with main
  • Loading branch information
amurKontur authored Oct 2, 2024
1 parent 900593d commit ef30345
Show file tree
Hide file tree
Showing 26 changed files with 161 additions and 154 deletions.
5 changes: 2 additions & 3 deletions e2e/location.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test } from './fixtures/test-options.ts';
import { getProjects } from './page-objects/helperBase.ts';
import type { Page } from '@playwright/test';
import type { Project } from './page-objects/helperBase.ts';
import type { PageManager } from './page-objects/pageManager.ts';

Expand All @@ -10,7 +9,7 @@ let projects = getProjects();
// Temporally switched off oam untill 18508 issue is fixed
// Atlas has no 'Locate me' feature for guest

projects = projects.filter((arg) => arg.name === 'smart-city');
projects = projects.filter((arg: Project) => arg.name === 'smart-city');

// Setting 3 retries for CI as it is very flacky with screenshots
const retriesNumber = process.env.CI ? 3 : 1;
Expand All @@ -19,7 +18,7 @@ test.describe.configure({ retries: retriesNumber });
// Moving test to a separate function to reuse it
const testLocation = async function (pageManager: PageManager, project: Project) {
await pageManager.atBrowser.openProject(project);
await pageManager.fromNavigationMenu.goToMap();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Map');
await (
await pageManager.atToolBar.getButtonByText('Locate me')
).click({ timeout: 15000 });
Expand Down
7 changes: 4 additions & 3 deletions e2e/locationWithPro.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test } from './fixtures/test-options.ts';
import { getProjects } from './page-objects/helperBase.ts';
import type { Page } from '@playwright/test';
import type { Project } from './page-objects/helperBase.ts';
import type { PageManager } from './page-objects/pageManager.ts';

Expand All @@ -9,7 +8,9 @@ let projects = getProjects();
// Temporally switched off disaster-ninja untill 15482 issue is fixed
// Temporally switched off oam untill 18508 issue is fixed

projects = projects.filter((arg) => arg.name !== 'disaster-ninja' && arg.name !== 'oam');
projects = projects.filter(
({ name }: Project) => name !== 'disaster-ninja' && name !== 'oam',
);

// Setting 3 retries for CI as it is very flacky with screenshots
const retriesNumber = process.env.CI ? 3 : 1;
Expand All @@ -18,7 +19,7 @@ test.describe.configure({ retries: retriesNumber });
// Moving test to a separate function to reuse it
const testLocation = async function (pageManager: PageManager, project: Project) {
await pageManager.atBrowser.openProject(project, { skipCookieBanner: true });
await pageManager.fromNavigationMenu.goToMap();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Map');
await (
await pageManager.atToolBar.getButtonByText('Locate me')
).click({ timeout: 15000 });
Expand Down
2 changes: 1 addition & 1 deletion e2e/locationWithUser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ for (const project of projects) {
pageManager,
}) => {
await pageManager.atBrowser.openProject(project, { skipCookieBanner: true });
await pageManager.fromNavigationMenu.goToMap();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Map');
await (
await pageManager.atToolBar.getButtonByText('Locate me')
).click({ timeout: 15000 });
Expand Down
10 changes: 3 additions & 7 deletions e2e/login.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { expect } from '@playwright/test';
import { test } from './fixtures/test-options.ts';
import { getProjects } from './page-objects/helperBase.ts';

Expand All @@ -7,24 +6,21 @@ const projects = getProjects();
// Create a loop to loop over all the projects and create a test for everyone
for (const project of projects) {
test(`As Guest, I can log in to ${project.title}, check that this profile is mine, and log out`, async ({
page,
pageManager,
}) => {
await pageManager.atBrowser.openProject(project);
await pageManager.fromNavigationMenu.goToLoginPage();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Login');
await pageManager.atLoginPage.typeLoginPasswordAndLogin(
process.env.EMAIL!,
process.env.PASSWORD!,
{ shouldSuccess: true, project },
);
// TO DO: activate this check once 19103 issue is done
// expect(page.url()).toContain('autotests');
pageManager.atBrowser.checkCampaignIsAutotest();
await pageManager.atProfilePage.checkLogoutBtnProfileTitleAndEmail(
process.env.EMAIL!,
);
await pageManager.atProfilePage.clickLogout();
// TO DO: activate this check once 19103 issue is done
// expect(page.url()).toContain('autotests');
pageManager.atBrowser.checkCampaignIsAutotest();
await pageManager.atProfilePage.checkLogoutBtnAndProfileAbsence();
await pageManager.atLoginPage.checkLoginAndSignupPresence();
});
Expand Down
5 changes: 3 additions & 2 deletions e2e/openOsm.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { expect } from '@playwright/test';
import { test } from './fixtures/test-options.ts';
import { getProjects } from './page-objects/helperBase.ts';
import type { Project } from './page-objects/helperBase.ts';

let projects = getProjects();

// Atlas has no 'Edit map in OSM' feature for guest
projects = projects.filter((arg) => arg.name !== 'atlas');
projects = projects.filter((arg: Project) => arg.name !== 'atlas');

for (const project of projects) {
test.fixme(
Expand All @@ -18,7 +19,7 @@ for (const project of projects) {
},
async ({ context, pageManager }) => {
await pageManager.atBrowser.openProject(project);
await pageManager.fromNavigationMenu.goToMap();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Map');

// TO DO: remove this action after 18582 issue is fixed
await pageManager.atMap.goToSpecificAreaByUrl(10.597, 53.9196, 27.5097, project);
Expand Down
6 changes: 3 additions & 3 deletions e2e/openOsmWithPro.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ for (const project of projects) {
});
});
await pageManager.atBrowser.openProject(project, { skipCookieBanner: true });
await pageManager.fromNavigationMenu.goToProfilePage();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Profile');
const osmEditorValue = await pageManager.atProfilePage.getOsmEditorValue();
expect(osmEditorValue).toBe('OpenStreetMap.org default editor');
});
Expand All @@ -48,7 +48,7 @@ test.describe(`As PRO User, I can use different OSM editors to open the map`, ()
pageManager,
}) => {
await pageManager.atBrowser.openProject(project, { skipCookieBanner: true });
await pageManager.fromNavigationMenu.goToProfilePage();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Profile');
const osmEditorValue = await pageManager.atProfilePage.getOsmEditorValue();
if (osmEditorValue !== editor) {
await pageManager.atProfilePage.setOsmEditorValue(editor);
Expand All @@ -57,7 +57,7 @@ test.describe(`As PRO User, I can use different OSM editors to open the map`, ()
expect(editedOsmEditorValue).toEqual(editor);
}

await pageManager.fromNavigationMenu.goToMap();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Map');

await pageManager.atMap.waitForTextBeingVisible('Toolbar');
await pageManager.atMap.waitForUrlToMatchPattern(/\?map=/i);
Expand Down
2 changes: 1 addition & 1 deletion e2e/openOsmWithUser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ for (const project of projects) {
pageManager,
}) => {
await pageManager.atBrowser.openProject(project, { skipCookieBanner: true });
await pageManager.fromNavigationMenu.goToMap();
await pageManager.atNavigationMenu.clickButtonToOpenPage('Map');

// TO DO: remove this action after 18582 issue is fixed
await pageManager.atMap.goToSpecificAreaByUrl(10.597, 53.9196, 27.5097, project);
Expand Down
75 changes: 70 additions & 5 deletions e2e/page-objects/helperBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ export class HelperBase {
project: Project,
{ skipCookieBanner = false, operablePage = this.page }: OpenProjectOptions = {},
) {
await operablePage.goto(project.url);
await operablePage.waitForLoadState();
await operablePage.goto(project.url, { waitUntil: 'commit' });
await Promise.all([
this.waitForEventWithFilter(operablePage, 'METRICS', 'router-layout-ready'),
operablePage.waitForLoadState(),
]);

// Expect correct app to be opened.
await this.waitForTextBeingVisible(`${project.title}`, operablePage);
Expand All @@ -47,6 +50,55 @@ export class HelperBase {
await operablePage.getByText('Accept optional cookies').click();
}

/**
* This method waits for event to be emitted and filtering to return true. It enters a browser console and waits for browser event to be emitted. If it is not emitted in several seconds, it fails the test.
* @param operablePage playwright page to use
* @param eventType event type to wait for
* @param eventName event name to wait for
* @returns event object
* @throws error if event is not emitted in time
*/

async waitForEventWithFilter(
operablePage: Page = this.page,
eventType: string,
eventName: string,
) {
// If you need to wait for other events using this method, try to use page.addInitScript instead of page.evaluate because page.evaluate does not support passing functions
const waitingTimeout = process.env.CI ? 40000 : 25000;
// Entering browser console to wait for event to be emitted
const filteredEvent: CustomEvent = await operablePage.evaluate(
(filterOptions) => {
return new Promise((resolve, reject) => {
// Reject Promise if event is late
const timeout = setTimeout(() => {
reject(
new Error(
`Timeout waiting for '${filterOptions.eventType}' event with '${filterOptions.eventName}' name in the browser (${filterOptions.waitingTimeout} ms) matching filtering condition (checking event.detail.name property)`,
),
);
}, filterOptions.waitingTimeout);

// Clear timeout if event is emitted matching filter, remove event listener and resolve Promise with event
const eventListener = (event: Event) => {
//@ts-expect-error if no detail property, we should fail, no way to pass function here (playwright limitation), but you can call such function, if it is added to HEAP using page.addInitScript
if (event?.detail?.name === filterOptions.eventName) {
clearTimeout(timeout);
globalThis.removeEventListener(filterOptions.eventName, eventListener);
resolve(event as CustomEvent);
}
};
globalThis.addEventListener(filterOptions.eventType, eventListener);
});
},
{ waitingTimeout, eventName, eventType },
);
expect(
filteredEvent,
`'${eventType}' event should be emitted and event.detail.name to equal '${eventName}'`,
).toBeDefined();
}

/**
* This method waits for a specific page to have a specific text
*/
Expand Down Expand Up @@ -91,10 +143,13 @@ export class HelperBase {

async compareUrlsAfterReload(project: Project) {
const currentUrl = this.page.url().replace(/\//g, '');
await this.page.reload({ waitUntil: 'load' });
await this.page.reload({ waitUntil: 'commit' });
await Promise.all([
this.waitForEventWithFilter(this.page, 'METRICS', 'router-layout-ready'),
this.page.waitForLoadState(),
]);
expect(this.page.url().replace(/\//g, '')).toEqual(currentUrl);
// TO DO: activate this check once 19103 issue is done
// await expect(this.page).toHaveTitle(`${project.title}`);
await expect(this.page).toHaveTitle(new RegExp(project.title));
}

/**
Expand All @@ -117,6 +172,16 @@ export class HelperBase {
async waitForUrlToMatchPattern(pattern: RegExp, page: Page = this.page) {
await page.waitForURL(pattern, { timeout: 30000 });
}

/**
* This method checks that campaign is autotests. It is needed for Google Analytics and other tracking services to differ normal users and autotests
*/

checkCampaignIsAutotest(): void {
expect(this.page.url(), 'URL should contain utm_campaign=autotests').toContain(
'utm_campaign=autotests',
);
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion e2e/page-objects/keycloakPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, request } from '@playwright/test';
import { expect } from '@playwright/test';
import { HelperBase } from './helperBase';
import type { Project } from './helperBase';
import type { Page, APIRequestContext } from '@playwright/test';
Expand Down
3 changes: 2 additions & 1 deletion e2e/page-objects/loginPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ export class LoginPage extends HelperBase {
async clickSignUpAndNavigateToKeycloak(context: BrowserContext) {
// Start waiting for new page being opened and click sign up
const [keycloakPage] = await Promise.all([
context.waitForEvent('page'),
context.waitForEvent('page', { timeout: 25000 }),
this.page.getByText('Sign up').click({ delay: 330 }),
]);
await keycloakPage.waitForLoadState();
await expect(keycloakPage).toHaveTitle(/Sign in/);
return keycloakPage;
}
Expand Down
6 changes: 5 additions & 1 deletion e2e/page-objects/mapPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ export class MapCanvas extends HelperBase {
project: Project,
) {
const urlForPlace = `${project.url}&map=${zoom}/${latitude}/${longitude}`;
await this.page.goto(urlForPlace, { waitUntil: 'domcontentloaded' });
await this.page.goto(urlForPlace, { waitUntil: 'commit' });
await Promise.all([
this.waitForEventWithFilter(this.page, 'METRICS', 'router-layout-ready'),
this.page.waitForLoadState(),
]);
await this.waitForZoom();
}

Expand Down
68 changes: 14 additions & 54 deletions e2e/page-objects/navigationMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,28 @@ import type { Page } from '@playwright/test';

export class NavigationMenu extends HelperBase {
/**
* This method allows to open map from navigation menu
* Opens a page by clicking a button in the navigation menu.
* @param buttonName - name of the button to click
* @param operablePage - playwright page to use
*/

async goToMap() {
// TO DO: replace locator here once 19141 task is done, refactor this logics
// When no need will be present to get locator every time
await this.waitForTextBeingVisible('Map');
await this.page.getByText('Map', { exact: true }).hover();
await this.page.getByText('Map', { exact: true }).click({ delay: 330 });
async clickButtonToOpenPage(buttonName: string, operablePage: Page = this.page) {
const button = operablePage
.getByTestId('side-bar')
.getByText(buttonName, { exact: true });
await button.hover();
// Delay is needed to emulate a real user click
await button.click({ delay: 500 });
}

/**
* This method checks that there is no map at navigation menu
*/

async checkThereIsNoMap() {
// TO DO: replace locator here once 19141 task is done
await this.page.locator('[value]').first().waitFor({ state: 'visible' });
await expect(this.page.locator('[value]').getByText('Map')).not.toBeVisible();
}

/**
* This method allows to open login page from navigation menu
*/

async goToLoginPage(operablePage: Page = this.page) {
// TO DO: replace locator here once 19141 task is done, refactor this logics
// When no need will be present to get locator every time
await operablePage.locator('[value="profile"]').first().waitFor({ state: 'visible' });
await operablePage.locator('[value="profile"]').getByText('Login').hover();
await operablePage
.locator('[value="profile"]')
.getByText('Login')
.click({ delay: 330 });
}

/**
* This method opens up the profile page from navigation menu
*/
async goToProfilePage() {
// TO DO: replace locator here once 19141 task is done, refactor this logics
// When no need will be present to get locator every time
await this.page.locator('[value="profile"]').first().waitFor({ state: 'visible' });
await this.page.locator('[value="profile"]').getByText('Profile').hover();
await this.page
.locator('[value="profile"]')
.getByText('Profile')
.click({ delay: 330 });
}

/**
* This method opens up the privacy page from navigation menu
*/

async goToPrivacyPage() {
// TO DO: replace locator here once 19141 task is done, refactor this logics
// When no need will be present to get locator every time
await this.page.locator('[value="privacy"]').first().waitFor({ state: 'visible' });
await this.page.locator('[value="privacy"]').getByText('Privacy').hover();
await this.page
.locator('[value="privacy"]')
.getByText('Privacy')
.click({ delay: 330 });
const sidebar = this.page.getByTestId('side-bar');
await expect(sidebar).toBeVisible();
const mapElement = sidebar.getByText('Map', { exact: true });
await expect(mapElement).not.toBeVisible();
}
}
4 changes: 2 additions & 2 deletions e2e/page-objects/pageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class PageManager {
return this.helperBase;
}

get fromNavigationMenu() {
get atNavigationMenu() {
return this.navigationMenu;
}

Expand Down Expand Up @@ -74,7 +74,7 @@ export class PageManager {

async auth(project: Project, email: string, password: string, operablePage: Page) {
await this.atBrowser.openProject(project, { operablePage });
await this.fromNavigationMenu.goToLoginPage(operablePage);
await this.atNavigationMenu.clickButtonToOpenPage('Login', operablePage);
await this.atLoginPage.typeLoginPasswordAndLogin(email, password, {
shouldSuccess: true,
project,
Expand Down
Loading

0 comments on commit ef30345

Please sign in to comment.