Skip to content

Commit

Permalink
feat(workbench-client/popup): add 'referrer' to popup handle to provi…
Browse files Browse the repository at this point in the history
…de information about the calling context

The popup handle, which can be obtained in a popup microfrontend, can be used to retrieve information about the calling context.

```ts
const referrer: WorkbenchPopupReferrer = Beans.get(WorkbenchPopup).referrer;
console.log('view', referrer.viewId); // reference to the view from which the popup was opened
console.log('view', referrer.viewCapabilityId); // reference to the view capability from which the popup was opened
```
  • Loading branch information
danielwiehl authored and Marcarrian committed Oct 11, 2022
1 parent edf6f53 commit 920d831
Show file tree
Hide file tree
Showing 19 changed files with 502 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@
</ng-template>
</sci-accordion>

<sci-accordion class="e2e-referrer" variant="solid">
<ng-template sciAccordionItem [panel]="panel_referrer">
<header>Referrer</header>
</ng-template>
<ng-template #panel_referrer>
<sci-form-field label="View ID">
<output class="e2e-view-id" [value]="popup.referrer.viewId ?? '<undefined>'"></output>
</sci-form-field>
<sci-form-field label="View Capability ID">
<output class="e2e-view-capability-id" [value]="popup.referrer.viewCapabilityId ?? '<undefined>'"></output>
</sci-form-field>
</ng-template>
</sci-accordion>

<sci-accordion class="return-value e2e-return-value" variant="solid">
<ng-template sciAccordionItem [panel]="panel_return_value">
<header>Return Value</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@
</ng-template>
</sci-accordion>

<sci-accordion class="e2e-referrer" variant="solid">
<ng-template sciAccordionItem [panel]="panel_referrer">
<header>Referrer</header>
</ng-template>
<ng-template #panel_referrer>
<sci-form-field label="View ID">
<output class="e2e-view-id" [value]="popup.referrer.viewId ?? '<undefined>'"></output>
</sci-form-field>
<sci-form-field label="View Capability ID">
<output class="e2e-view-capability-id" [value]="popup.referrer.viewCapabilityId ?? '<undefined>'"></output>
</sci-form-field>
</ng-template>
</sci-accordion>

<sci-accordion class="return-value e2e-return-value" variant="solid">
<ng-template sciAccordionItem [panel]="panel_return_value">
<header>Return Value</header>
Expand Down
17 changes: 12 additions & 5 deletions projects/scion/e2e-testing/src/helper/element-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,27 @@ import {coerceArray} from './testing.util';
export namespace ElementSelectors {

/**
* Returns the selector for the <iframe> contained within the <sci-router-outlet> element with the given name or that has set the given CSS class(es).
* Selects the <sci-router-outlet> element with the given name or that has set the given CSS class(es).
*/
export function routerOutletFrame(outletNameOrSelector: string | RouterOutletSelector): string {
export function routerOutlet(outletNameOrSelector: string | RouterOutletSelector): string {
if (typeof outletNameOrSelector === 'string') {
return `sci-router-outlet[name="${outletNameOrSelector}"] iframe`;
return `sci-router-outlet[name="${outletNameOrSelector}"]`;
}
if (outletNameOrSelector.outletName) {
return `sci-router-outlet[name="${outletNameOrSelector.outletName}"] iframe`;
return `sci-router-outlet[name="${outletNameOrSelector.outletName}"]`;
}
if (outletNameOrSelector.cssClass) {
return `sci-router-outlet.${coerceArray(outletNameOrSelector.cssClass).join('.')} iframe`;
return `sci-router-outlet.${coerceArray(outletNameOrSelector.cssClass).join('.')}`;
}
throw Error('[RouterOutletSelectorError] Missing required outlet name or CSS class');
}

/**
* Selects the iframe of the <sci-router-outlet> element with the given name or that has set the given CSS class(es).
*/
export function routerOutletFrame(outletNameOrSelector: string | RouterOutletSelector): string {
return `${routerOutlet(outletNameOrSelector)} iframe`;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (c) 2018-2022 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

import {expect} from '@playwright/test';
import {test} from '../fixtures';
import {PopupOpenerPagePO} from './page-object/popup-opener-page.po';
import {ViewPagePO} from './page-object/view-page.po';
import {RegisterWorkbenchIntentionPagePO} from './page-object/register-workbench-intention-page.po';
import {HostPopupPagePO} from './page-object/host-popup-page.po';

test.describe('Workbench Popup', () => {

test.describe('Popup Referrer', () => {

test('should have a view reference to the contextual view', async ({appPO, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

// TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271
// https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271

// register intention
const registerIntentionPagePO = await microfrontendNavigator.openInNewTab(RegisterWorkbenchIntentionPagePO, 'app1');
await registerIntentionPagePO.registerIntention({type: 'popup', qualifier: {component: 'host-popup'}});

const popupOpenerPagePO = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1');
await popupOpenerPagePO.enterQualifier({component: 'host-popup'});
await popupOpenerPagePO.clickOpen();

const popupPagePO = new HostPopupPagePO(appPO, 'host-popup');
await expect(await popupPagePO.getReferrer()).toEqual({
viewId: popupOpenerPagePO.viewId,
viewCapabilityId: await popupOpenerPagePO.outlet.getCapabilityId(),
});
});

test('should have a view reference to the specified contextual view', async ({appPO, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

// TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271
// https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271

// register intention
const registerIntentionPagePO = await microfrontendNavigator.openInNewTab(RegisterWorkbenchIntentionPagePO, 'app1');
await registerIntentionPagePO.registerIntention({type: 'popup', qualifier: {component: 'host-popup'}});

const startPagePO = await appPO.openNewViewTab();
const startPageViewId = startPagePO.viewId!;

const popupOpenerPagePO = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1');
await popupOpenerPagePO.enterQualifier({component: 'host-popup'});
await popupOpenerPagePO.enterContextualViewId(startPageViewId);
await popupOpenerPagePO.enterCloseStrategy({closeOnFocusLost: false});
await popupOpenerPagePO.clickOpen({waitForPopup: false});

await startPagePO.view!.viewTab.click();

const popupPagePO = new HostPopupPagePO(appPO, 'host-popup');
await expect(await popupPagePO.getReferrer()).toEqual({
viewId: startPageViewId,
});
});

test('should have a view reference to the specified contextual view and capability', async ({appPO, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

// TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271
// https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271

// register intention
const registerIntentionPagePO = await microfrontendNavigator.openInNewTab(RegisterWorkbenchIntentionPagePO, 'app1');
await registerIntentionPagePO.registerIntention({type: 'popup', qualifier: {component: 'host-popup'}});

const microfrontendPO = await microfrontendNavigator.openInNewTab(ViewPagePO, 'app1');
const microfrontendViewId = microfrontendPO.viewId!;

const popupOpenerPagePO = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1');
await popupOpenerPagePO.enterQualifier({component: 'host-popup'});
await popupOpenerPagePO.enterContextualViewId(microfrontendViewId);
await popupOpenerPagePO.enterCloseStrategy({closeOnFocusLost: false});
await popupOpenerPagePO.clickOpen({waitForPopup: false});

await microfrontendPO.view!.viewTab.click();

const popupPagePO = new HostPopupPagePO(appPO, 'host-popup');
await expect(await popupPagePO.getReferrer()).toEqual({
viewId: microfrontendViewId,
viewCapabilityId: await microfrontendPO.outlet.getCapabilityId(),
});
});

test('should not have a view reference if opened outside of a contextual view', async ({appPO, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

// TODO [#271]: Register popup capability in the host app via RegisterWorkbenchCapabilityPagePO when implemented the issue #271
// https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues/271

// register intention
const registerIntentionPagePO = await microfrontendNavigator.openInNewTab(RegisterWorkbenchIntentionPagePO, 'app1');
await registerIntentionPagePO.registerIntention({type: 'popup', qualifier: {component: 'host-popup'}});

const popupOpenerPagePO = await microfrontendNavigator.openInNewTab(PopupOpenerPagePO, 'app1');
await popupOpenerPagePO.enterQualifier({component: 'host-popup'});
await popupOpenerPagePO.enterContextualViewId('<null>');
await popupOpenerPagePO.clickOpen();

const popupPagePO = new HostPopupPagePO(appPO, 'host-popup');
await expect(await popupPagePO.getReferrer()).toEqual({});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
* SPDX-License-Identifier: EPL-2.0
*/

import {isPresent} from '../../helper/testing.util';
import {isPresent, withoutUndefinedEntries} from '../../helper/testing.util';
import {AppPO} from '../../app.po';
import {PopupPO} from '../../popup.po';
import {PopupSize} from '@scion/workbench';
import {Params} from '@angular/router';
import {WorkbenchPopupCapability} from '@scion/workbench-client';
import {WorkbenchPopupCapability, WorkbenchPopupReferrer} from '@scion/workbench-client';
import {SciAccordionPO} from '../../components.internal/accordion.po';
import {SciPropertyPO} from '../../components.internal/property.po';
import {Locator} from '@playwright/test';
Expand Down Expand Up @@ -51,7 +51,7 @@ export class HostPopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-popup-capability'));
await accordionPO.expand();
try {
return JSON.parse(await this._locator.locator('div.e2e-popup-capability').innerText());
return JSON.parse(await accordionPO.locator('div.e2e-popup-capability').innerText());
}
finally {
await accordionPO.collapse();
Expand All @@ -62,7 +62,7 @@ export class HostPopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-popup-params'));
await accordionPO.expand();
try {
return await new SciPropertyPO(this._locator.locator('sci-property.e2e-popup-params')).readProperties();
return await new SciPropertyPO(accordionPO.locator('sci-property.e2e-popup-params')).readProperties();
}
finally {
await accordionPO.collapse();
Expand All @@ -73,7 +73,21 @@ export class HostPopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-route-params'));
await accordionPO.expand();
try {
return await new SciPropertyPO(this._locator.locator('sci-property.e2e-route-params')).readProperties();
return await new SciPropertyPO(accordionPO.locator('sci-property.e2e-route-params')).readProperties();
}
finally {
await accordionPO.collapse();
}
}

public async getReferrer(): Promise<WorkbenchPopupReferrer> {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-referrer'));
await accordionPO.expand();
try {
return withoutUndefinedEntries({
viewId: await accordionPO.locator('output.e2e-view-id').innerText(),
viewCapabilityId: await accordionPO.locator('output.e2e-view-capability-id').innerText(),
});
}
finally {
await accordionPO.collapse();
Expand All @@ -93,7 +107,7 @@ export class HostPopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-return-value'));
await accordionPO.expand();
try {
await this._locator.locator('input.e2e-return-value').fill(returnValue);
await accordionPO.locator('input.e2e-return-value').fill(returnValue);
}
finally {
await accordionPO.collapse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {SciAccordionPO} from '../../components.internal/accordion.po';
import {SciCheckboxPO} from '../../components.internal/checkbox.po';
import {Locator} from '@playwright/test';
import {ElementSelectors} from '../../helper/element-selectors';
import {SciRouterOutletPO} from './sci-router-outlet.po';

/**
* Page object to interact {@link PopupOpenerPageComponent}.
Expand All @@ -27,9 +28,11 @@ export class PopupOpenerPagePO {
private readonly _locator: Locator;

public readonly view: ViewPO;
public readonly outlet: SciRouterOutletPO;

constructor(private _appPO: AppPO, public viewId: string) {
this.view = _appPO.view({viewId});
this.outlet = new SciRouterOutletPO(_appPO, viewId);
this._locator = _appPO.page.frameLocator(ElementSelectors.routerOutletFrame(viewId)).locator('app-popup-opener-page');
}

Expand Down Expand Up @@ -93,7 +96,7 @@ export class PopupOpenerPagePO {
}
}

public async enterContextualViewId(viewId: string | '<null>'| '<default>'): Promise<void> {
public async enterContextualViewId(viewId: string | '<null>' | '<default>'): Promise<void> {
await this._locator.locator('input.e2e-contextual-view-id').fill(viewId);
}

Expand Down Expand Up @@ -127,17 +130,18 @@ export class PopupOpenerPagePO {
await accordionPO.collapse();
}

public async clickOpen(): Promise<void> {
public async clickOpen(options?: {waitForPopup?: boolean}): Promise<void> {
const expectedPopupIndex = await this._appPO.popupLocator().count();

await this._locator.locator('button.e2e-open').click();

// Evaluate the response: resolve the promise on success, or reject it on error.
const errorLocator = this._locator.locator('output.e2e-popup-error');
return Promise.race([
this._appPO.popupLocator().nth(expectedPopupIndex).waitFor({state: 'visible'}),
errorLocator.waitFor({state: 'attached'}).then(() => errorLocator.innerText()).then(error => Promise.reject(Error(error))),
]);
if (options?.waitForPopup ?? true) {
// Evaluate the response: resolve the promise on success, or reject it on error.
const errorLocator = this._locator.locator('output.e2e-popup-error');
await Promise.race([
this._appPO.popupLocator().nth(expectedPopupIndex).waitFor({state: 'visible'}),
errorLocator.waitFor({state: 'attached'}).then(() => errorLocator.innerText()).then(error => Promise.reject(Error(error))),
]);
}
}

public async getPopupCloseAction(): Promise<PopupCloseAction> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
* SPDX-License-Identifier: EPL-2.0
*/

import {fromRect} from '../../helper/testing.util';
import {fromRect, withoutUndefinedEntries} from '../../helper/testing.util';
import {AppPO} from '../../app.po';
import {PopupPO} from '../../popup.po';
import {PopupSize} from '@scion/workbench';
import {Params} from '@angular/router';
import {WorkbenchPopupCapability} from '@scion/workbench-client';
import {WorkbenchPopupCapability, WorkbenchPopupReferrer} from '@scion/workbench-client';
import {SciAccordionPO} from '../../components.internal/accordion.po';
import {SciPropertyPO} from '../../components.internal/property.po';
import {ElementSelectors} from '../../helper/element-selectors';
Expand Down Expand Up @@ -41,7 +41,7 @@ export class PopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-popup-capability'));
await accordionPO.expand();
try {
return JSON.parse(await this._locator.locator('div.e2e-popup-capability').innerText());
return JSON.parse(await accordionPO.locator('div.e2e-popup-capability').innerText());
}
finally {
await accordionPO.collapse();
Expand All @@ -52,7 +52,7 @@ export class PopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-popup-params'));
await accordionPO.expand();
try {
return await new SciPropertyPO(this._locator.locator('sci-property.e2e-popup-params')).readProperties();
return await new SciPropertyPO(accordionPO.locator('sci-property.e2e-popup-params')).readProperties();
}
finally {
await accordionPO.collapse();
Expand All @@ -63,7 +63,7 @@ export class PopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-route-params'));
await accordionPO.expand();
try {
return await new SciPropertyPO(this._locator.locator('sci-property.e2e-route-params')).readProperties();
return await new SciPropertyPO(accordionPO.locator('sci-property.e2e-route-params')).readProperties();
}
finally {
await accordionPO.collapse();
Expand All @@ -74,7 +74,7 @@ export class PopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-route-query-params'));
await accordionPO.expand();
try {
return await new SciPropertyPO(this._locator.locator('sci-property.e2e-route-query-params')).readProperties();
return await new SciPropertyPO(accordionPO.locator('sci-property.e2e-route-query-params')).readProperties();
}
finally {
await accordionPO.collapse();
Expand All @@ -85,7 +85,21 @@ export class PopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-route-fragment'));
await accordionPO.expand();
try {
return this._locator.locator('span.e2e-route-fragment').innerText();
return accordionPO.locator('span.e2e-route-fragment').innerText();
}
finally {
await accordionPO.collapse();
}
}

public async getReferrer(): Promise<WorkbenchPopupReferrer> {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-referrer'));
await accordionPO.expand();
try {
return withoutUndefinedEntries({
viewId: await accordionPO.locator('output.e2e-view-id').innerText(),
viewCapabilityId: await accordionPO.locator('output.e2e-view-capability-id').innerText(),
});
}
finally {
await accordionPO.collapse();
Expand All @@ -105,7 +119,7 @@ export class PopupPagePO {
const accordionPO = new SciAccordionPO(this._locator.locator('sci-accordion.e2e-return-value'));
await accordionPO.expand();
try {
await this._locator.locator('input.e2e-return-value').fill(returnValue);
await accordionPO.locator('input.e2e-return-value').fill(returnValue);
}
finally {
await accordionPO.collapse();
Expand Down
Loading

0 comments on commit 920d831

Please sign in to comment.