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 cypress-e2e/fixtures/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ export class Constants {
consumerCoreSectionButton: 'Button imported from /core',
},
otherSectionCodeBlock:
'{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }',
'{\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n}',
consumerSection: {
header: 'This is /consumer.',
importMessages: {
Expand Down
233 changes: 220 additions & 13 deletions playwright-e2e/common/base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, Locator, Page } from '@playwright/test';
import { expect, Locator, Page, test } from '@playwright/test';
import type { ElementHandle } from 'playwright';

interface VisibilityOptions {
Expand All @@ -25,6 +25,29 @@ interface ClickWithTextOptions {
parentSelector?: string;
isTargetChanged?: boolean;
index?: number;
wait?: number;
}

interface BrowserAlertOptions {
selector: string;
alertMessage?: string;
isEqual?: boolean;
index?: number;
parentSelector?: string;
wait?: number;
}

interface BrowserAlertForMultipleHostsOptions extends BrowserAlertOptions {
host: number;
}

interface CompareHostsOptions {
selector: string;
extraHost: number;
isEqual?: boolean;
index?: number;
clickSelector?: string;
wait?: number;
}

interface ElementContainTextOptions {
Expand Down Expand Up @@ -68,8 +91,16 @@ export class BaseMethods {
constructor(protected readonly page: Page) {}

private resolveLocator(selector: string, options: { parentSelector?: string; text?: string; index?: number } = {}): Locator {
return this.resolveLocatorForPage(this.page, selector, options);
}

private resolveLocatorForPage(
page: Page,
selector: string,
options: { parentSelector?: string; text?: string; index?: number } = {},
): Locator {
const { parentSelector, text, index } = options;
let locator = parentSelector ? this.page.locator(parentSelector).locator(selector) : this.page.locator(selector);
let locator = parentSelector ? page.locator(parentSelector).locator(selector) : page.locator(selector);

if (text) {
locator = locator.filter({ hasText: text });
Expand Down Expand Up @@ -200,7 +231,7 @@ export class BaseMethods {
await expect(locator).not.toHaveCount(0);
}

async clickElementWithText({ selector, text, parentSelector, isTargetChanged = false, index }: ClickWithTextOptions): Promise<void> {
async clickElementWithText({ selector, text, parentSelector, isTargetChanged = false, index, wait }: ClickWithTextOptions): Promise<void> {
const locator = this.resolveLocator(selector, { parentSelector, text, index });
const element = locator.first();

Expand All @@ -209,6 +240,10 @@ export class BaseMethods {
}

await element.click();

if (wait && wait > 0) {
await this.page.waitForTimeout(wait);
}
}

async checkElementContainText({ selector, text, isContain = true, index, parentSelector }: ElementContainTextOptions): Promise<void> {
Expand Down Expand Up @@ -364,30 +399,190 @@ export class BaseMethods {
await poller.not.toContain(urlPart);
}

skipTestByCondition(condition: unknown, reason: string = 'Skipped by condition'): void {
if (condition) {
test.info().skip(reason);
}
}

async checkBrowserAlertByText({
selector,
alertMessage,
isEqual = true,
index = 0,
parentSelector,
wait = 0,
}: BrowserAlertOptions): Promise<void> {
const locator = this.resolveLocator(selector, { parentSelector, index });

if (wait > 0) {
await this.page.waitForTimeout(wait);
}

const message = await this.captureDialogMessage(this.page, locator.first());

if (alertMessage !== undefined) {
if (isEqual) {
expect(message).toBe(alertMessage);
} else {
expect(message).not.toBe(alertMessage);
}
}
}

async checkBrowserAlertForMultipleHosts({
selector,
alertMessage,
isEqual = true,
index = 0,
parentSelector,
host,
wait = 0,
}: BrowserAlertForMultipleHostsOptions): Promise<void> {
const baseGroup = this.resolveLocator(selector, { parentSelector });
const baseCount = await baseGroup.count();

if (baseCount === 0) {
throw new Error(`No elements found for selector "${selector}" on the base page.`);
}

const targetIndex = Math.min(index, baseCount - 1);

if (wait > 0) {
await this.page.waitForTimeout(wait);
}

const baseMessage = await this.captureDialogMessage(this.page, baseGroup.nth(targetIndex));

if (alertMessage !== undefined) {
if (isEqual) {
expect(baseMessage).toBe(alertMessage);
} else {
expect(baseMessage).not.toBe(alertMessage);
}
}

const remotePage = await this.page.context().newPage();

try {
await remotePage.goto(`http://localhost:${host}/`, { waitUntil: 'networkidle' });

const remoteGroup = this.resolveLocatorForPage(remotePage, selector, { parentSelector });
const remoteCount = await remoteGroup.count();

if (remoteCount === 0) {
throw new Error(`No elements found for selector "${selector}" on host ${host}.`);
}

const remoteIndex = Math.min(targetIndex, remoteCount - 1);
const remoteMessage = await this.captureDialogMessage(remotePage, remoteGroup.nth(remoteIndex));

if (wait > 0) {
await remotePage.waitForTimeout(wait);
}

if (isEqual) {
if (alertMessage !== undefined) {
expect(remoteMessage).toBe(alertMessage);
}

expect(remoteMessage).toBe(baseMessage);
} else {
if (alertMessage !== undefined) {
expect(remoteMessage).not.toBe(alertMessage);
}

expect(remoteMessage).not.toBe(baseMessage);
}
} finally {
await remotePage.close();
}
}

async compareInfoBetweenHosts(
selector: string,
extraHost: number,
isEqual: boolean = true,
index: number = 0,
clickSelector?: string,
wait: number = 0,
selectorOrOptions: string | CompareHostsOptions,
extraHostArg?: number,
isEqualArg: boolean = true,
indexArg: number = 0,
clickSelectorArg?: string,
waitArg: number = 0,
): Promise<void> {
const baseLocator = this.page.locator(selector).nth(index);
const baseText = (await baseLocator.innerText()).trim();
let selector: string;
let extraHost: number;
let isEqual: boolean;
let index: number;
let clickSelector: string | undefined;
let wait: number;

if (typeof selectorOrOptions === 'string') {
selector = selectorOrOptions;
if (typeof extraHostArg !== 'number') {
throw new Error('The "extraHost" parameter must be provided when using the positional signature.');
}
extraHost = extraHostArg;
isEqual = isEqualArg;
index = indexArg;
clickSelector = clickSelectorArg;
wait = waitArg;
} else {
({ selector, extraHost, isEqual = true, index = 0, clickSelector, wait = 0 } = selectorOrOptions);
}

const baseGroup = this.page.locator(selector);
const baseCount = await baseGroup.count();

if (baseCount === 0) {
throw new Error(`No elements found for selector "${selector}" on the base page.`);
}

const targetIndex = Math.min(index, baseCount - 1);

if (wait > 0) {
await this.page.waitForTimeout(wait);
}

const baseText = (await baseGroup.nth(targetIndex).innerText()).trim();

const remotePage = await this.page.context().newPage();

try {
await remotePage.goto(`http://localhost:${extraHost}/`, { waitUntil: 'networkidle' });

if (clickSelector) {
await remotePage.locator(clickSelector).click();
const remoteClickGroup = remotePage.locator(clickSelector);
const remoteClickCount = await remoteClickGroup.count();

if (remoteClickCount === 0) {
throw new Error(`No elements found for selector "${clickSelector}" on host ${extraHost}.`);
}

const clickIndex = Math.min(targetIndex, remoteClickCount - 1);

try {
await this.captureDialogMessage(remotePage, remoteClickGroup.nth(clickIndex));
} catch (error) {
const isTimeoutError = error instanceof Error && /Timeout/.test(error.message);
if (isTimeoutError) {
await remoteClickGroup.nth(clickIndex).click();
} else {
throw error;
}
}

if (wait > 0) {
await remotePage.waitForTimeout(wait);
}
}

const remoteText = (await remotePage.locator(selector).nth(index).innerText()).trim();
const remoteGroup = remotePage.locator(selector);
const remoteCount = await remoteGroup.count();

if (remoteCount === 0) {
throw new Error(`No elements found for selector "${selector}" on host ${extraHost}.`);
}

const remoteIndex = Math.min(targetIndex, remoteCount - 1);
const remoteText = (await remoteGroup.nth(remoteIndex).innerText()).trim();

if (isEqual) {
expect(remoteText).toBe(baseText);
Expand All @@ -414,4 +609,16 @@ export class BaseMethods {
return style.getPropertyValue(property as string);
}, prop);
}

private async captureDialogMessage(page: Page, locator: Locator): Promise<string> {
const [dialog] = await Promise.all([
page.waitForEvent('dialog', { timeout: 5_000 }),
locator.click(),
]);

const message = dialog.message();
await dialog.accept();

return message;
}
}
2 changes: 1 addition & 1 deletion playwright-e2e/fixtures/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ export class Constants {
consumerCoreSectionButton: 'Button imported from /core',
},
otherSectionCodeBlock:
'{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }',
'{\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n}',
consumerSection: {
header: 'This is /consumer.',
importMessages: {
Expand Down
1 change: 1 addition & 0 deletions vue-cli/consumer/vue.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;

module.exports = {
lintOnSave: false,
publicPath: 'http://localhost:8080/',
configureWebpack: {
optimization: {
Expand Down
1 change: 1 addition & 0 deletions vue-cli/core/vue.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;

module.exports = {
lintOnSave: false,
publicPath: 'http://localhost:9000/',
configureWebpack: {
optimization: {
Expand Down
19 changes: 11 additions & 8 deletions vue-cli/e2e/methods/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@ export class VueCliMethods extends BaseMethods {
isContain: false,
});

await this.clickElementWithText({
selector: baseSelectors.tags.coreElements.button,
text: Constants.elementsText.vueCliApp.buttonsText.otherSectionButton,
});
const buttonLocator = this.page
.locator(baseSelectors.tags.section)
.locator(baseSelectors.tags.coreElements.button)
.filter({ hasText: Constants.elementsText.vueCliApp.buttonsText.otherSectionButton })
.first();

await this.checkElementVisibility({
parentSelector: baseSelectors.tags.section,
selector: baseSelectors.tags.code,
});
const [dialog] = await Promise.all([
this.page.waitForEvent('dialog', { timeout: 5_000 }),
buttonLocator.click(),
]);

await dialog.accept();

await this.checkElementWithTextPresence({
parentSelector: baseSelectors.tags.section,
Expand Down
7 changes: 4 additions & 3 deletions vue-cli/e2e/tests/consumerAppChecks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ test.describe('Vue CLI', () => {
isContain: false,
});

await basePage.clickElementWithText({
await basePage.checkBrowserAlertByText({
parentSelector: baseSelectors.tags.section,
selector: baseSelectors.tags.coreElements.button,
text: Constants.elementsText.vueCliApp.buttonsText.otherSectionButton,
wait: 1500,
alertMessage: Constants.commonPhrases.vueCliApp.otherAppAlertMessage,
index: 1,
});

await basePage.compareInfoBetweenHosts({
Expand Down
3 changes: 0 additions & 3 deletions vue-cli/e2e/tests/runAll.spec.ts

This file was deleted.

Loading
Loading