Skip to content
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

feat: sap build workzone std ed enablement #520

Merged
merged 17 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
9 changes: 9 additions & 0 deletions .github/workflows/wdi5-tests_auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ env:
wdi5_password: ${{secrets.BTP_PASSWORD}}
wdi5_one_password: ${{secrets.BTP_PASSWORD}}
wdi5_two_password: ${{secrets.BTP_PASSWORD}}
wdi5_wz_username: ${{secrets.WZ_USER}}
wdi5_wz_password: ${{secrets.WZ_PASSWORD}}
BROWSERSTACK_USERNAME: ${{secrets.BROWSERSTACK_USERNAME}}
BROWSERSTACK_ACCESS_KEY: ${{secrets.BROWSERSTACK_ACCESS_KEY}}
SAUCE_USERNAME: ${{secrets.SAUCE_USERNAME}}
Expand Down Expand Up @@ -67,3 +69,10 @@ jobs:

- name: (browserstack) btp/sap cloud id, basic auth, office 365, custom auth
run: BROWSERSTACK=true npm run test:auth

# these two run against the deployed CAP SFLIGHT sample app in BTP WorkZone
- name: test lib support for workzone
run: npm run test:wz:testlib

- name: regular support for workzone
run: npm run test:wz:regular
70 changes: 69 additions & 1 deletion docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
`wdi5` currently support these authentication mechanisms and/or providers:

- [SAP Cloud IdP (default BTP Identity Provider)](#sap-cloud-idp-default-btp-identity-provider)
- [SAP Cloud Identity Services - Identity Authentication (IAS)](#sap-cloud-identity-services-identity-authentication)
- [Office 365](#office-365)
- [custom IdP](#custom-idp)
- [Basic Authentication](#basic-authentication)

Generally speaking, the authentication behavior mimicks that of a regular user session: first, the `baseUrl` (from the `wdio.conf.(j|t)s`-file) is opened in the configured browser. Then, the redirect to the Authentication provider is awaited and [the credentials](#credentials) are supplied.

BTP-, Office365- and custom IdP all supply credentials as a user would, meaning they're literally typed into the respective input fields on each login screen.
BTP-, IAS-, Office365- and custom IdP all supply credentials as a user would, meaning they're literally typed into the respective input fields on each login screen.
Basic Authentication prepends username and password in encoded form to the URL, resulting in an `HTTP` `GET` in the form of `https://username:[email protected]`.

!> Multi-Factor Authentication is not supported as it's nearly impossible to manage any media break (e.g. browser ↔ mobile) in authentication flows out of the box
Expand Down Expand Up @@ -122,6 +123,73 @@ capabilities: {

The `BTP` authenticator will automatically detect whether the login process is a two-step- (first username needs to be supplied, the password) or a single-step (both username and password are supplied on one screen) sequence.

### SAP Cloud Identity Services - Identity Authentication

?> only available in `wdi5` >= 2

Using the 'Identity Authentication Service (IAS) Authenticator' in `wdi5` is a subset of the [above BTP Authentication](#sap-cloud-idp-default-btp-identity-provider).
It takes the same configuration options, plus `disableBiometricAuth` (default: `true`, which you want in almost all cases) and `idpDomain`. The latter is necessary to satisfy cookie conditions in the remote-controlled browser.
Set `idpDomain` to the _domain-only_ part of your IAS tenant URL, e.g. `weiruhg.accounts.ondemand.com`, _omitting_ the protocol prefix (`https://`).

!> If `disableBiometricAuth` is set to `true`, `idpDomain` must be set as well!

<!-- tabs:start -->

#### **single browser**

```js
baseUrl: "https://your-deployed-ui5-on-btp.app",
capabilities: {
// browserName: "..."
"wdi5:authentication": {
provider: "BTP", //> mandatory
usernameSelector: "#j_username", //> optional; default: "#j_username"
passwordSelector: "#j_password", //> optional; default: "#j_password"
submitSelector: "#logOnFormSubmit", //> optional; default: "#logOnFormSubmit"
disableBiometricAuth: true, //> optional; default: true
idpDomain: "weiruhg.accounts.ondemand.com", //> mandatory if disableBiometricAuth = true, otherwise optional; no default
}
}
```

#### **multiremote**

```js
baseUrl: "https://your-deployed-ui5-on-btp.app",
capabilities: {
// "one" is the literal reference to a browser instance
one: {
capabilities: {
// browserName: "..."
"wdi5:authentication": {
provider: "BTP", //> mandatory
usernameSelector: "#j_username", //> optional; default: "#j_username"
passwordSelector: "#j_password", //> optional; default: "#j_password"
submitSelector: "#logOnFormSubmit", //> optional; default: "#logOnFormSubmit"
disableBiometricAuth: true, //> optional; default: true
idpDomain: "weiruhg.accounts.ondemand.com", //> mandatory if disableBiometricAuth = true, otherwise optional; no default
}
}
},
// "two" is the literal reference to a browser instance
two: {
capabilities: {
// browserName: "..."
"wdi5:authentication": {
provider: "BTP", //> mandatory
usernameSelector: "#j_username", //> optional; default: "#j_username"
passwordSelector: "#j_password", //> optional; default: "#j_password"
submitSelector: "#logOnFormSubmit", //> optional; default: "#logOnFormSubmit"
disableBiometricAuth: true, //> optional; default: true
idpDomain: "weiruhg.accounts.ondemand.com", //> mandatory if disableBiometricAuth = true, otherwise optional; no default
}
}
}
}
```

<!-- tabs:end -->

### Office 365

<!-- tabs:start -->
Expand Down
8 changes: 7 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ exports.config = {
screenshotsDisabled: false, // [optional] {boolean}, default: false; if set to true, screenshots won't be taken and not written to file system
logLevel: "verbose", // [optional] error | verbose | silent, default: "error"
skipInjectUI5OnStart: false, // [optional] {boolean}, default: false; true when UI5 is not on the start page, you need to later call <wdioUI5service>.injectUI5() manually
waitForUI5Timeout: 15000 // [optional] {number}, default: 15000; maximum waiting time in milliseconds while checking for UI5 availability
waitForUI5Timeout: 15000, // [optional] {number}, default: 15000; maximum waiting time in milliseconds while checking for UI5 availability
btpWorkZoneEnablement: false // [optional] {boolean}, default: false; whether to instruct wdi5 to inject itself in both the SAP Build Workzone, standard edition, shell and app
}
// ...
}
Expand Down Expand Up @@ -125,6 +126,11 @@ Number in milliseconds (default: `15000`) to wait for UI5-related operations wit

?> Setting this timeout to 30 seconds or higher requires the [session script timeout](https://webdriver.io/docs/timeouts/#session-script-timeout) to be increased as well.

### `btpWorkZoneEnablement`

Boolean setting to trigger injecting `wdi5` into both the shell and the app when used with the SAP Build Workzone, standard edition.
Recommended complement is to also [configure IAS Authentication](authentication?id=sap-cloud-identity-services-identity-authentication): as SAP Build requires its own Identity Provider (most likely provided by using an IAS tenant), you'll have to configure authentication against that as well in `wdi5`.

## `package.json`

Not required, but as a convention, put a `test` or `wdi5` script into your UI5.app's `package.json` to start `wdi5/wdio`.
Expand Down
79 changes: 79 additions & 0 deletions docs/fe-testlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,85 @@ it("I enter custom data", async () => {
})
```

## Using the test library with SAP Build Workzone, standard edition

?> only available in `wdi5` >= 2

The SAP Build Workzone, standard edition, runs the Fiori shell and the Fiori Elements app in separate `iframe`s. For operating on both shell and app, `wdi5` injects itself in both as well. This can be triggered by configuring `wdi5` with `btpWorkZoneEnablement` set to `true`:

```js
// typescript syntax sample
export const config: wdi5Config = {
wdi5: {
btpWorkZoneEnablement: true,
logLevel: "verbose"
},
// ... additional config
}
```

Most likely, a [`wdi5` authentication configuration for IAS](authentication?id=sap-cloud-identity-services-identity-authentication) is also needed to authenticate against the IAS tenant the SAP Build Workzone, standard edition, is running with.

Then, point the `baseUrl` in your `wdio.conf.(j|t)s` against _the app URL_ in Workzone, e.g. `https://your.launchpad.cfapps.eu10.hana.ondemand.com/site/ymmv#travel-process`:

```js
export const config: wdi5Config = {
wdi5: {
btpWorkZoneEnablement: true,
logLevel: "verbose"
},
baseUrl: "https://your.launchpad.cfapps.eu10.hana.ondemand.com/site/ymmv#travel-process",
// ... additional config
}
```

?> It is important to use the URL pointing to the app under test, as this is assumed by `wdi5` to be the start of its injection process

After making `wdi5` aware of the Workzone setting, now inject the testlibrary as usual in the `before` hook of the test suite:

```js
// sample page obects from the CAP SFLIGHT app
describe("drive in Work Zone with testlib support", () => {
let FioriElementsFacade
before(async () => {
FioriElementsFacade = await browser.fe.initialize({
onTheMainPage: {
ListReport: {
appId: "sap.fe.cap.travel",
componentId: "TravelList",
entitySet: "Travel"
}
},
onTheDetailPage: {
ObjectPage: {
appId: "sap.fe.cap.travel",
componentId: "TravelObjectPage",
entitySet: "Travel"
}
},
onTheItemPage: {
ObjectPage: {
appId: "sap.fe.cap.travel",
componentId: "BookingObjectPage",
entitySet: "Booking"
}
},
onTheShell: {
Shell: {}
}
})
})

it("...", async () => {
await FioriElementsFacade.execute((Given, When, Then) => {
Then.onTheMainPage.iSeeThisPage()
})
})
// further its
```

See the files in [`wdi5`'s git repo at `/examples/ui5-ts-app/test/e2e/workzone/**/*`](https://github.com/ui5-community/wdi5/tree/main/examples/ui5-ts-app/test/e2e/workzone) for a sample config and test!

## Troubleshooting

### Enable verbose mode for test library output
Expand Down
2 changes: 1 addition & 1 deletion examples/fe-app/wdio.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ exports.config = {
wdi5: {
screenshotPath: join("app", "incidents", "webapp", "wdi5-test", "__screenshots__"),
logLevel: "verbose", // error | verbose | silent
waitForUI5Timeout: 90000
waitForUI5Timeout: 30000
},
//// wdio runner config
specs: [join("webapp", "wdi5-test", "**/*.test.js")],
Expand Down
1 change: 1 addition & 0 deletions examples/ui5-ts-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"//test-h:authentication": "run-s \"authentication:* -- --headless\"",
"test:authentication:bstack": "BROWSERSTACK=true run-s authentication:*",
"test:lateInject": "wdio run wdio-ui5-late.conf.ts",
"test:wz": "wdio run test/e2e/workzone/wdio-ui5-workzone.conf.ts",
"authentication:btp": "wdio run test/e2e/authentication/wdio-btp-authentication.conf.ts",
"authentication:basic-auth": "wdio run test/e2e/authentication/wdio-basic-auth-authentication.conf.ts",
"authentication:custom": "wdio run test/e2e/authentication/wdio-custom-authentication.conf.ts",
Expand Down
36 changes: 20 additions & 16 deletions examples/ui5-ts-app/test/e2e/Input.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import Input from "sap/m/Input"
import { wdi5Selector } from "wdio-ui5-service"

const inputSelector: wdi5Selector = {
selector: {
id: "mainUserInput",
viewName: "test.Sample.tsapp.view.Main"
}
}

describe("Input", async () => {
it("should read name from username field", async () => {
const inputText: wdi5Selector = {
selector: {
id: "mainUserInput",
viewName: "test.Sample.tsapp.view.Main"
}
}
const input = await browser.asControl<Input>(inputText)
const input = await browser.asControl<Input>(inputSelector)
const value = await input.getValue()
expect(value).toEqual("Helvetius Nagy")
})

it("should check if the field is writeable", async () => {
const inputText: wdi5Selector = {
selector: {
id: "mainUserInput",
viewName: "test.Sample.tsapp.view.Main"
}
}
await (browser.asControl(inputText) as unknown as Input).setValue("Smith Smithersson")
const input = await (browser.asControl(inputText) as unknown as Input).getValue()
expect(input).toEqual("Smith Smithersson")
const newValue = "Smith Smithersson"
await browser.asControl<Input>(inputSelector).setValue(newValue)
const input = await browser.asControl<Input>(inputSelector).getValue()
expect(input).toEqual(newValue)
})

it("should retrieve the webcomponent's bound path via a managed object", async () => {
const control = await browser.asControl(inputSelector)
const bindingInfo = await control.getBindingInfo("value")
// @ts-ignore
const parts = await bindingInfo.parts
expect(parts[0].path).toEqual("/Customers('TRAIH')/ContactName")
})
})
5 changes: 5 additions & 0 deletions examples/ui5-ts-app/test/e2e/workzone/regular-journey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe.skip("drive in Work Zone with standard wdi5/wdio APIs", () => {
it("should see the List Report page", async () => {})

it("should see the Object Pages load and then returns to list", async () => {})
})
59 changes: 59 additions & 0 deletions examples/ui5-ts-app/test/e2e/workzone/testlib-journey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
describe("drive in Work Zone with testlib support", () => {
let FioriElementsFacade
before(async () => {
FioriElementsFacade = await browser.fe.initialize({
onTheMainPage: {
ListReport: {
appId: "sap.fe.cap.travel",
componentId: "TravelList",
entitySet: "Travel"
}
},
onTheDetailPage: {
ObjectPage: {
appId: "sap.fe.cap.travel",
componentId: "TravelObjectPage",
entitySet: "Travel"
}
},
onTheItemPage: {
ObjectPage: {
appId: "sap.fe.cap.travel",
componentId: "BookingObjectPage",
entitySet: "Booking"
}
},
onTheShell: {
Shell: {}
}
})
})

it("should see the List Report page", async () => {
await FioriElementsFacade.execute((Given, When, Then) => {
Then.onTheMainPage.iSeeThisPage()
})
})

it("should see the Object Pages load and then returns to list", async () => {
await FioriElementsFacade.execute((Given, When, Then) => {
When.onTheMainPage.onTable().iPressRow(1)
Then.onTheDetailPage.iSeeThisPage()

When.onTheDetailPage.onTable({ property: "to_Booking" }).iPressRow({ BookingID: "1" })
Then.onTheItemPage.iSeeThisPage()

// When.onTheShell.iNavigateBack() // beh, b/c wrong iframe
})

await FioriElementsFacade.onTheShell.iNavigateBack()
await FioriElementsFacade.onTheShell.iNavigateBack()

// REVISIT: we want the testlib to expose its back navigation capability
// ~ could look like: await wdi5.wz.iNavigateBack()

await FioriElementsFacade.execute((Given, When, Then) => {
Then.onTheMainPage.iSeeThisPage()
})
})
})
Loading
Loading