diff --git a/client-side-js/injectUI5.cjs b/client-side-js/injectUI5.cjs index 98219577..0474c578 100644 --- a/client-side-js/injectUI5.cjs +++ b/client-side-js/injectUI5.cjs @@ -233,6 +233,7 @@ async function clientSide_injectUI5(config, waitForUI5Timeout, browserInstance) * extract the multi use function to get a UI5 Control from a JSON Webobejct */ window.wdi5.getUI5CtlForWebObj = (ui5Control) => { + //> REVISIT: refactor to https://ui5.sap.com/#/api/sap.ui.core.Element%23methods/sap.ui.core.Element.closestTo for UI5 >= 1.106 return jQuery(ui5Control).control(0) } diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 3c214fdb..e1927ed3 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,5 +1,6 @@ - [Home](/) - [Installation](installation.md) +- [Migration](migration.md) - [Configuration](configuration.md) - [Running](running.md) - [Debugging](debugging.md) diff --git a/docs/configuration.md b/docs/configuration.md index 0a0f0f7d..f70a45bd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,6 +6,8 @@ With `wdi5` being an extension ("service") to WebdriverIO (`wdio`), first and fo Of course the configuration file name can be changed arbitrarily. But then it needs to be specified explicitly when [running `wdi5`/`wdio`](running), e.g. `$> wdio run myconfig.js`. +?> `wdi5` can be used both in a [CJS-](https://nodejs.org/docs/latest/api/modules.html) and an [ESM-](https://nodejs.org/docs/latest/api/esm.html)environment. The code examples sometimes use either or, but in no favor of one over the other. + ## `wdi5` All options go into a top-level `wdi5` object in `wdio.conf.(j|t)s`, diff --git a/docs/contributing.md b/docs/contributing.md index 48c2f1ee..1764d12c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -15,15 +15,9 @@ ```shell # use reference node version $> nvm use -# you need npm 7+ → we're using npm's workspaces feature -$> npm --version -# eventually update npm itself with command: -$> npm install -g npm@latest # will also install all deps in workspaces + setup pre-commit hooks $> npm i -# build entire proj once -$> npm run build -# turn on build watcher +# turn on build watcher for both esm and cjs $> npm run build:watch ``` @@ -41,22 +35,28 @@ $> npm run _startApp:js # run a single test with wdi5/wdio ### runs the "test:websever" script from /examples/ui5-js-app/package.json ### in workspace "examples/ui5-js-app" -### but only any test file in /examples/ui5-js-app/webapp/test/e2e/**/* that -### matches "basic" in the filename -### and run in watch mode (browser stays open, test reruns when file changes) +### but only the one test file (./webapp/test/e2e/basic.test.js) +### in watch mode (browser stays open, test reruns when file changes) ### for true TDD -$> npm run test:webserver -w examples/ui5-js-app -- --spec basic --watch +$> npm run test:webserver \ +-w examples/ui5-js-app \ +-- --spec ./webapp/test/e2e/basic.test.js \ +--watch ``` ## prerequisites -- **npm >= 7** - because we're using npm's `workspaces` feature +- **node >= 18** - LTS version and because we're using npm's `workspaces` feature - [`nvm`](https://github.com/nvm-sh/nvm) (whatever the Windows equivalent is) to use a dedicated Node.js version - be accustomed to conventional commits ## set up the dev env -Although `wdi5`'s core is written in TypeScript, we're favoring it mainly for type generation: the resulting `*.d.ts` files make using `wdi5` in UI5 Projects both in JS and TS more comfortable, as they're providing code completion and parameter-/usage-hints. +Although `wdi5`'s core is written in TypeScript as an ESM module. +This was done mainly for ease of module export (CJS and ESM) and type generation. + +**CJS** module code is output in `/cjs`, **ESM** module code in (you might have guessed it) `/esm`. +Additionally, a `package.json` is auto-generated by the build process into `/cjs`. First, check the `workspaces` defined in `package.json`: they denote the "modules"/"projects" contained in here. Most prominently is `"."`, which refers to `wdi5` (aka `wdio-ui5-service`) itself. @@ -92,14 +92,14 @@ Most prominently is `"."`, which refers to `wdi5` (aka `wdio-ui5-service`) itsel There are two main parts to `wdi5`: -0. the **Node.js runtime**, coded in `/src`, transpiled to `/dist` -1. the **browser-scope runtime**, in `/client-side-js` +0. the **Node.js runtime**, coded in `/src`, transpiled to `/cjs` and `/esm` +1. the **browser-scope runtime**, in `/client-side-js` (note the `*.cjs` file endings b/c `wdi5` itself is an ESM module first) -`wdi5` as `wdio-ui5-service` is started via WebdriverIO's `before` hook, in `/src/service.ts`. -Then, the Node.js-browser bridge is injected client-side in `/client-side-js/injectUI5.js`. +`wdi5` as `wdio-ui5-service` is started via WebdriverIO's `before` hook, in `/src/service.ts`. +Then, the Node.js-browser bridge is injected client-side in `/client-side-js/injectUI5.cjs`. Subsequently, the Node.js runtime launches client-side JS execution via WebdriverIO's `browser.executeAsync()` api. -A UI5 control from the browser-scope is represented in `wdi5` in the Node.js-scope in `/src/lib/wdi5-control.ts`. +A UI5 control from the browser-scope is represented in `wdi5` in the Node.js-scope in `/src/lib/wdi5-control.ts`. All browser-scope commands (such as `browser.asControl()`) are provided with `/src/lib/wdi5-bridge.ts`'s `addWdi5Commands()` method. `wdi5` comes with it's own general-purpose Logger that can be used in the Node.js scope: `/src/lib/Logger.ts`. @@ -128,35 +128,36 @@ recommended approach: ```shell # run a single test with wdi5/wdio - ### runs the "test:websever" script from /examples/ui5-js-app/package.json ### in workspace "examples/ui5-js-app" - ### but only any test file in /examples/ui5-js-app/webapp/test/e2e/**/* that - ### matches "basic" in the filename - ### and run in watch mode (browser stays open, test reruns when file changes) + ### but only the one test file (./webapp/test/e2e/basic.test.js) + ### in watch mode (browser stays open, test reruns when file changes) ### for true TDD - $> npm run test:webserver -w examples/ui5-js-app -- --spec basic --watch + $> npm run test:webserver \ + -w examples/ui5-js-app \ + -- --spec ./webapp/test/e2e/basic.test.js \ + --watch ``` or use `wdio` directly for executing the test(s): ```shell $> cd examples/ui5-js-app - # run with locally installed wdio - $> ../../node_modules/.bin/wdio run wdio-ui5tooling.conf.js --spec basic - # if you have wdio installed globally - $> wdio run wdio-ui5tooling.conf.js --spec basic + # run with locally installed wdio (./../node_modules/.bin/) + $> npx wdio run wdio-ui5tooling.conf.js --spec ./webapp/test/e2e/basic.test.js ``` Also [utilize `mocha`'s `.only`](https://mochajs.org/#exclusive-tests) for isolating one or more single test(s) or suite(s) to run. -?> when working in test in one of the sample apps (`/examples/...`) and you don't seem to be getting the latest changes you did in `wdi5`, -"reinstall" `wdi5` (`wdio-ui5-service`) in the respective `npm` workspace by doing `$> npm i` in the project root. +?> when working on tests in one of the sample apps (`/examples/...`) and you don't seem to be getting the latest changes you did in `wdi5`, +first make sure a build is still running (`npm run build:watch`) +and eventually "reinstall" `wdi5` (`wdio-ui5-service`) in the respective `npm` workspace by doing `$> npm i` in the project root. ## commiting changes -We're using `prettier` and `eslint` for code-formatting and validation. -Staged files are linted and formatted according to the specs in `.prettierrc` and `.eslintrc.js`. -`git` commit messages are linted to comply with "conventional commits" . Additionally, the message subject `wip` is allowed: +We're using `prettier` and `eslint` for code-formatting and validation. +Staged files are linted and formatted according to the specs in `.prettierrc` and `/src/.eslintrc.cjs`. +`git` commit messages are linted to comply with "conventional commits" . +In addition to the message subjects defined in "conventional commits", `wip` is allowed: ```shell wip(optional scope): the subject of the message @@ -176,11 +177,8 @@ Please issue your Pull Requests against the `main` branch of the repository. ## work on the docs -All documentation is written in `markdown` and lives in `/docs`. -[`Docsify` is used](https://docsify.js.org/#/) for running the documentation GitHub pages site . It can easily be used to also run locally to work and preview the documentation site. - -Install it globally: `$> npm i -g docsify-cli` -Then serve the `docs` dir: `$> docsify serve ./docs` +All documentation is written in `markdown` and lives in `/docs`. +[`Docsify` is used](https://docsify.js.org/#/) for running the documentation GitHub pages site . It can easily be used to also run locally to work and preview the documentation site: `npm run startDoc`. This will run the documentation site including live reload on diff --git a/docs/docker.md b/docs/docker.md index b0f47dcf..eae0f28d 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -49,7 +49,7 @@ Note that as of now, only `Google Chrome` is inlcuded as a web browser for runni ### composed scenario -(soon) +(not yet) ## docker image(s) setup @@ -66,4 +66,4 @@ Note that as of now, only `Google Chrome` is inlcuded as a web browser for runni ### swarm -(soon) +(not yet) diff --git a/docs/img/01_installation.png b/docs/img/01_installation.png index eabb0d2c..1cce00f1 100644 Binary files a/docs/img/01_installation.png and b/docs/img/01_installation.png differ diff --git a/docs/img/03_installation.png b/docs/img/03_installation.png deleted file mode 100644 index 3e1f90f0..00000000 Binary files a/docs/img/03_installation.png and /dev/null differ diff --git a/docs/img/05_installation.png b/docs/img/05_installation.png index e1852897..dda6064d 100644 Binary files a/docs/img/05_installation.png and b/docs/img/05_installation.png differ diff --git a/docs/img/07_installation.png b/docs/img/07_installation.png index df7bed19..f9fa30cf 100644 Binary files a/docs/img/07_installation.png and b/docs/img/07_installation.png differ diff --git a/docs/img/09_installation.png b/docs/img/09_installation.png index dfabb2a5..6fdd7e88 100644 Binary files a/docs/img/09_installation.png and b/docs/img/09_installation.png differ diff --git a/docs/installation.md b/docs/installation.md index 7bd63b1c..37485209 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,12 +2,14 @@ With `wdi5` [being a service to WebdriverIO](https://webdriver.io/docs/wdio-ui5-service), installation of both is required. +`wdi5` can be used both in a [CJS-](https://nodejs.org/docs/latest/api/modules.html) and an [ESM-](https://nodejs.org/docs/latest/api/esm.html)environment. + ## Prerequisites - UI5 app running in the browser, accessible via `http(s)://host.ext:port`. Recommended tooling for this is either the official [UI5 tooling](https://github.com/SAP/ui5-tooling) (`ui5 serve`) or some standalone http server like [`soerver`](https://github.com/vobu/soerver) or [`http-server`](https://www.npmjs.com/package/http-server). - UI5 app using UI5 >= `1.60` (because the [`RecordReplay` API](https://ui5.sap.com/sdk/#/api/sap.ui.test.RecordReplay) is used extensively which is only available from UI5 `1.60`+) -- Node.js version >= `16` (`lts/gallium`) +- Node.js version >= `18` (`lts/hydrogen`) The installation of `wdi5` and WebdriverIO can either be done by using (a) `npm init wdi5`, (b) the [Webdriver.IO `cli`](https://webdriver.io/docs/gettingstarted.html) or (c) manually. @@ -76,8 +78,6 @@ examples: ## b) guided install via `wdio` cli -!> `wdi5` is not compatible with `wdio 8` at this time. If you have an existing `wdio` project you will have to downgrade your existing `wdio` packages to version `7.x.x` manually. If you are starting a new `wdio` project with this guide, you will have to change the package versions to be compatible with version `7.x.x` for all wdio related pacakges. - ?> This step assumes that you have neither installed WebdriverIO nor `wdi5` previously for that UI5 app. If so, see [manual install](#manual-installation) below. On the console, change into the folder with the UI5 app you want to test. @@ -103,7 +103,8 @@ $> npx wdio ``` ![start of the wdi5/wdio installation process](./img/01_installation.png) -![choosing mocha as the syntax for writing tests](./img/03_installation.png) + +Answer each step of the guided install subsequently. !> Don't forget to check `ui5` at the stage "Do you want to add a service to your test setup?" @@ -126,7 +127,7 @@ At the end of the guided installation, you'll be greated with a message similar ## c) manual installation -In case you already have a `wdio.conf.(j|t)s` file, getting `wdi5` is a straightforward process. +In case you already have a `wdio.conf.(j|t)s` file and WebdriverIO 8 installed, getting `wdi5` is a straightforward process. In a terminal, change to the directory holding your `wdio` conf file. @@ -145,7 +146,6 @@ Add that service to the respective section in `wdio.conf.(j|t)s`: ```javascript //... services: [ - // other services like 'chromedriver' // ... "ui5" ] diff --git a/docs/migration.md b/docs/migration.md index 939ae21c..2edeeb2e 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,11 +2,11 @@ ## from `^1` to `^2` -Version >= 2 of `wdi5` is WebdriverIO v8 compatible. This entails a move to ESM as primary module, with CJS compatibility ensured. +Version >= 2 of `wdi5` is WebdriverIO v8 compatible. This means that the primary module has moved to ESM, but CJS compatibility is still ensured. ### no more explicit browser driver needed! -WebdriverIO now automates downloading and starting the appropriate driver corresponding to the `browser` specified in the `capabilites` section of the config file (see also https://webdriver.io/blog/2023/07/31/driver-management): +WebdriverIO now automates downloading and starting the appropriate driver corresponding to the `browser` specified in the `capabilites` section of the config file (see also [Take a seat, WebdriverIO is driving for you!](https://webdriver.io/blog/2023/07/31/driver-management) ): ```diff - services: ["chromedriver", "ui5"], @@ -17,7 +17,7 @@ WebdriverIO now automates downloading and starting the appropriate driver corres }] ``` -Setting `browserName: "chrome"` is enough to tell `wdi5`/`wdio` to run the tests with the stable version of Chrome - no more `"chromedriver"` needed in the `services`! +Setting `broswerName: "chrome"` is enough to tell `wdi5`/`wdio` to run the tests with the stable version of Chrome - no more `"chromedriver"` needed in the `services`! To switch to Safari on macOS, following the above example is as easy as changing the `browserName`: ```js @@ -31,7 +31,7 @@ To switch to Safari on macOS, following the above example is as easy as changing ?> this is an _optional_ change - `wdi5` will continue to work also with explicitly denoting the browser driver in `services`! -### check file system location reference of spec files in wdio.config.(j|t)s +### check file system location reference of spec files in `wdio.config.(j|t)s` directory references start from the directory the config file is in now, not from `cwd` or project root: diff --git a/docs/recipes.md b/docs/recipes.md index 37e03346..de3ce62a 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -12,7 +12,7 @@ The config is set so that - a static download dir exists ```javascript -// in wdio.conf.cjs +// in wdio.conf.js "goog:chromeOptions": { prefs: { directory_upgrade: true, @@ -140,15 +140,47 @@ await browser In VS Code, use a `jsconfig.json` at the root of your JavaScript-project, at the very least containing + + +#### **JavaScript (CJS)** + ```json { "compilerOptions": { - "types": ["node", "webdriverio/async", "wdio-ui5-service/dist"] + "types": [ + "node", + "@openui5/types", + "@wdio/globals/types", + "@wdio/mocha-framework", + "wdio-ui5-service/cjs", + "expect-webdriverio" + ] } } ``` -See an example at `/examples/ui5-js-app/jsconfig.json` in the wdi5 repository. +See an example at [`/examples/ui5-js-app/jsconfig.json` in the wdi5 repository](https://github.com/ui5-community/wdi5/blob/main/examples/ui5-js-app/jsconfig.json). + +#### **JavaScript (ESM)** + +```json +{ + "compilerOptions": { + "types": [ + "node", + "@openui5/types", + "@wdio/globals/types", + "@wdio/mocha-framework", + "wdio-ui5-service/esm", + "expect-webdriverio" + ] + } +} +``` + +See an example at [`/examples/ui5-js-app-esm/jsconfig.json` in the wdi5 repository](https://github.com/ui5-community/wdi5/blob/main/examples/ui5-js-app-esm/jsconfig.json). + + ## DevX: (JS) cast to proper type for code completion @@ -182,37 +214,80 @@ Another example: trigger a `sap.m.List`'s aggregation function... ![screenshot of code completion at coding-time by using the proper JSDoc type cast](./img/jsdoc-type-cast-codecompletion.png) -## navigate an FLP tile - -Feasible if your project launches as a mocked Fiori Launchpad (FLP). +## navigate in SAP Build WorkZone -Given the FLP is also a UI5 app, `wdi5`‘s standard mechanism of retrieving controls could be used in the tests. +### via standard `wdi5` mechanisms -Via the text property matcher, the tile‘s label is located (a `sap.m.Text`). +**First**, adjust the config to enable Build Workzone support in `wdi5`[via `btpWorkZoneEnablement`](configuration#btpworkzoneenablement). This will inject `wdi5` both in the shell and in the "app area" of Workzone. -```javascript -const tile = await browser.asControl({ - selector: { - properties: { - text: "SAP Community Profile Picture Editor" - }, - controlType: "sap.m.Text" +```typescript +export const config: wdi5Config = { + wdi5: { + btpWorkZoneEnablement: true, + logLevel: "verbose" } -}) + //... +} ``` -Then we navigate up the control tree with the help of [`wdi5`'s fluent async api](usage#fluent-async-api) until we reach the tile itself via `.getParent().getParent()`. -A click on it brings us to the linked app itself. +If `wdi5` logLevel is set to `verbose`, the console will reflect this: -```javascript -// get wdio element reference -const $tile = await tile.getWebElement() -// use wdio api -await $tile.click() +```console +[0-0] [wdi5] delegating wdi5 injection to WorkZone enablement... +# ... +[0-0] [wdi5] injected wdi5 into the WorkZone std ed's shell! +# ... +[0-0] [wdi5] injected wdi5 into the WorkZone std ed's iframe containing the target app! +# ... +``` + +**Second**, point `baseUrl` in the config to the _app under test_ in Workzone, not only to the Workzone URL! + +```typescript +export const config: wdi5Config = { + wdi5: { + btpWorkZoneEnablement: true, + logLevel: "verbose" + }, + // note the "hash"ed URL part at the end pointing to the app! + baseUrl: "https://your.launchpad.cfapps.eu10.hana.ondemand.com/site/you#travel-process" + //... +} +``` + +**Third**, in the actual test(s), switch between Workzone shell and Workzone's app area via `wdi5`'s convenience methods `toWorkZoneShell()` and `toWorkZoneApp()`. + +```typescript +import { wdi5 } from "wdio-ui5-service" +describe("drive in Work Zone with standard wdi5/wdio APIs", () => { + it("shell", async () => { + await wdi5.toWorkZoneShell() // <-- + await browser + .asControl({ + selector: { + id: "userActionsMenuHeaderButton" + } + }) + .press() + // ... + }) + + it("should find the table in the travel app", async () => { + await wdi5.toWorkZoneApp() // <-- + const table = await browser.asControl({ + selector: { + id: "sap.fe.cap.travel::TravelList--fe::table::Travel::LineItem" + } + }) + // ... + }) + //... +}) ``` -Why not locating that tile itself directly? -Because in most cases it doesn't have a stable ID, and thus its‘ ID might change in the next UI5 rendering cycle - using a locator id such as `__tile0` might break the test eventually then. +### in test library + +There's an integration for `wdi5` explained in the [respective documentation chapter](fe-testlib#using-the-test-library-with-sap-build-workzone-standard-edition). ## send keyboard events to a Control/element @@ -318,7 +393,7 @@ it("should find the search button on a SearchField", async () => { ## using wdio functions -WebdriverIO has an extensive element [API](https://webdriver.io/docs/api/). The [Element API] specifically can be quite useful to check if the page elements are in a certain state e.g. [isDisplayed](https://webdriver.io/docs/api/element/isDisplayed) or [isClickable](https://webdriver.io/docs/api/element/isClickable). +WebdriverIO has an extensive element [API](https://webdriver.io/docs/api/). The [Element API](https://webdriver.io/docs/api/element) specifically can be quite useful to check if the page elements are in a certain state e.g. [isDisplayed](https://webdriver.io/docs/api/element/isDisplayed) or [isClickable](https://webdriver.io/docs/api/element/isClickable). To make use of these element functions, `wdi5` allows to switch APIs from UI5 to wdio by calling `$()`. diff --git a/docs/usage.md b/docs/usage.md index e1566d3a..4f1ff76e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -40,6 +40,8 @@ See below for many more examples on both using `wdi5`- and `wdio`-APIs, denoting The files containing tests should reside in `$ui5-app/webapp/test/` and be named `*.test.(j|t)s`. Yet both test file directory and naming pattern can be specified [via WebdriverIO's `specs`](https://webdriver.io/docs/options#specs) in [`wdio.conf.(j|t)s`](/configuration#wdi5). +?> `wdi5` can be used both in a [CJS-](https://nodejs.org/docs/latest/api/modules.html) and an [ESM-](https://nodejs.org/docs/latest/api/esm.html)environment. The code examples sometimes use either or, but in no favor of one over the other. + ### Test suites WebdriverIO and `wdi5` can be used with [Mocha](http://mochajs.org/), [Jasmine](http://jasmine.github.io/), and [Cucumber](https://cucumber.io/), with Mocha being used in [all examples](https://github.com/ui5-community/wdi5/tree/main/examples) in `wdi5`. @@ -48,7 +50,7 @@ Mocha tests [are structured with `describe`-blocks ("suite"), containing `it`s ( -#### **JavaScript** +#### **JavaScript (CJS)** ```js const { wdi5 } = require("wdio-ui5-service") @@ -71,6 +73,29 @@ describe("test suite description", () => { }) ``` +#### **JavaScript (ESM)** + +```js +import { wdi5 } from "wdio-ui5-service" + +describe("test suite description", () => { + before(async () => { + await wdi5.goTo("#/Page") + }) + + it("should do this", async () => { + const selector = { + /* ... */ + } + const prop = await browser.asControl(selector).getProperty("...") + expect(prop).toEqual("...") + }) + it("should do that", async () => { + //... + }) +}) +``` + #### **TypeScript** ```ts @@ -381,7 +406,7 @@ If `getAggregation` is called via a shorthand such as `sap.m.ListBase.getItems() ### `enterText` -`enterText(sText)`: input `sText` into a (input-capable) control via [EnterText](https://ui5.sap.com/#/api/sap.ui.test.actions.EnterText) +`enterText(text)`: input `text` (`string`) into a (input-capable) control via [EnterText](https://ui5.sap.com/#/api/sap.ui.test.actions.EnterText) ```javascript const control = await browser.asControl(inputSelector) @@ -528,7 +553,7 @@ Example: `5-5-17-46-47-screenshot--some-id.png` For convenient console output, use `wdi5.getLogger('tag')`. It supports the `syslog`-like levels `log`, `info`, `warn` and `error`: ```javascript -const wdi5 = require("wdi5") +const { wdi5 } = require("wdio-ui5-service") wdi5.getLogger().log("any", "number", "of", "log", "parts") ```