@appsemble/e2e 0.34.2 → 0.34.5-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,45 @@
1
- # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.34.2/config/assets/logo.svg) Appsemble End 2 End Tests
1
+ # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.34.5-test.0/config/assets/logo.svg) Appsemble End 2 End Tests
2
2
 
3
- > Run end 2 end tests on an Appsemble environment
3
+ > Run end 2 end tests on an Appsemble environment and provide Appsemble fixtures
4
4
 
5
- [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.34.2/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.34.2)
5
+ [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.34.5-test.0/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.34.5-test.0)
6
6
  [![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io)
7
7
 
8
8
  ## Table of Contents
9
9
 
10
+ - [How to install](#how-to-install)
10
11
  - [Usage](#usage)
12
+ - [Appsemble end-to-end tests](#appsemble-end-to-end-tests)
13
+ - [Authentication](#authentication)
14
+ - [Contribution](#contributing)
11
15
  - [License](#license)
12
16
 
17
+ ## How to install
18
+
19
+ You can install the package by running the following at the root of your project:
20
+
21
+ ```sh
22
+ npm install @appsemble/e2e
23
+ ```
24
+
13
25
  ## Usage
14
26
 
27
+ To use the fixtures in your tests, you can import `expect` or `test` at the top of the file.
28
+
29
+ These fixtures can be added to any test by specifying them in the test declaration. Matchers don't
30
+ need to be manually added.
31
+
32
+ ```ts
33
+ import { expect, test } from '@appsemble/e2e';
34
+
35
+ test('should navigate to app', async ({ visitApp }) => {
36
+ await visitApp('Example app');
37
+ await expect(page.getByText('Example app')).toBeVisible();
38
+ });
39
+ ```
40
+
41
+ ## Appsemble end-to-end tests
42
+
15
43
  To run end to end tests, run:
16
44
 
17
45
  ```sh
@@ -23,6 +51,11 @@ The tests use [Playwright](https://playwright.dev/).
23
51
  After each test a video will be saved in the `packages/e2e/test-results/{name-test}` directory for
24
52
  further inspection.
25
53
 
54
+ On the first retry of a failed job, its trace will be uploaded to the same directory. This trace can
55
+ be viewed locally, or it can be uploaded to the
56
+ [online trace viewer](https://trace.playwright.dev/). This shows you step-by-step whawt happened
57
+ during the test as if you ran it using `ui mode`.
58
+
26
59
  The end to end tests use the following environment variables:
27
60
 
28
61
  - `BOT_ACCOUNT_EMAIL` — The email address used to login.
@@ -32,13 +65,7 @@ The end to end tests use the following environment variables:
32
65
  request review environment instead.
33
66
  - `ACCESS_TOKEN` — The access token of the account that's logged in.
34
67
 
35
- ### Fixtures
36
-
37
- Common end-to-end fixtures are defined under the `fixtures` directory. These are exported for other
38
- packages to use, like client apps. This can be done by installing this package and importing `test`
39
- or `expect`.
40
-
41
- ### Authentication
68
+ ## Authentication
42
69
 
43
70
  Some fixtures call the Appsemble API using the Playwright `request` object. This expects you to have
44
71
  set an access token as environment variable with the name **ACCESS_TOKEN** beforehand. The easiest
@@ -60,5 +87,5 @@ When writing end-to-end tests, have a look at the
60
87
 
61
88
  ## License
62
89
 
63
- [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.34.2/LICENSE.md) ©
90
+ [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.34.5-test.0/LICENSE.md) ©
64
91
  [Appsemble](https://appsemble.com)
@@ -1,9 +1,20 @@
1
- import { type App } from '@appsemble/types';
1
+ import { type App, type CreateAppOptions, type PatchAppOptions } from '@appsemble/types';
2
2
  export interface AppFixtures {
3
3
  /**
4
- * Create an app.
4
+ * Create a new app for the given organization with the provided options
5
+ *
6
+ * @param organizationId ID of the organization to create the app for
7
+ * @param options Options to create the app with
8
+ * @returns The created app
9
+ */
10
+ createApp: (organizationId: string, yaml: string, options?: CreateAppOptions) => Promise<App>;
11
+ /**
12
+ * Patches the given app with the provided options
13
+ *
14
+ * @param appId ID of the app to patch
15
+ * @param options What to patch in the app
5
16
  */
6
- createApp: (organization: string, yaml: string) => Promise<App>;
17
+ patchApp: (appId: number, options: PatchAppOptions) => Promise<App>;
7
18
  /**
8
19
  * Set the role of the user.
9
20
  *
@@ -3,12 +3,19 @@ import stripIndent from 'strip-indent';
3
3
  import { expect } from '../../expect/index.js';
4
4
  export const test = base.extend({
5
5
  async createApp({ request }, use) {
6
- await use(async (organization, yaml) => {
6
+ await use(async (organizationId, yaml, options) => {
7
7
  const formData = new FormData();
8
- formData.append('OrganizationId', organization.toLowerCase());
8
+ formData.append('OrganizationId', organizationId);
9
9
  formData.append('yaml', stripIndent(yaml));
10
+ if (options) {
11
+ for (const [option, value] of Object.entries(options)) {
12
+ if (value !== undefined) {
13
+ formData.append(option, String(value));
14
+ }
15
+ }
16
+ }
10
17
  const response = await request.post('/api/apps', {
11
- data: formData,
18
+ multipart: formData,
12
19
  headers: {
13
20
  Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
14
21
  },
@@ -19,6 +26,26 @@ export const test = base.extend({
19
26
  return app;
20
27
  });
21
28
  },
29
+ async patchApp({ request }, use) {
30
+ await use(async (appId, options) => {
31
+ const formData = new FormData();
32
+ for (const [option, value] of Object.entries(options)) {
33
+ if (value !== undefined) {
34
+ formData.append(option, String(value));
35
+ }
36
+ }
37
+ const response = await request.patch(`/api/apps/${appId}`, {
38
+ multipart: formData,
39
+ headers: {
40
+ Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
41
+ },
42
+ });
43
+ expect(response.status()).toBe(200);
44
+ const app = (await response.json());
45
+ expect(app).not.toBeNull();
46
+ return app;
47
+ });
48
+ },
22
49
  async changeAppMemberRole({ browser }, use) {
23
50
  await use(async (appId, role) => {
24
51
  const page = await browser.newPage();
@@ -6,10 +6,12 @@ export interface DemoAppFixtures {
6
6
  */
7
7
  reseedDemoApp: (appId: number) => Promise<void>;
8
8
  /**
9
- * Login to an Appsemble demo app.
9
+ * Creates a new demo account with the given role and logs into the app with that account.
10
10
  *
11
- * @param role The role to login in as.
11
+ * > **Warning**: Expects you to be in the login screen of a demo app
12
+ *
13
+ * @param role The role to of the account to create
12
14
  */
13
- loginDemoApp: (role: string) => Promise<void>;
15
+ createAndLoginDemoAppMember: (role: string) => Promise<void>;
14
16
  }
15
17
  export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & DemoAppFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -1,4 +1,4 @@
1
- import { test as base } from '@playwright/test';
1
+ import { test as base, expect } from '@playwright/test';
2
2
  export const test = base.extend({
3
3
  async reseedDemoApp({ browser }, use) {
4
4
  await use(async (appId) => {
@@ -9,13 +9,11 @@ export const test = base.extend({
9
9
  await page.close();
10
10
  });
11
11
  },
12
- async loginDemoApp({ page }, use) {
12
+ async createAndLoginDemoAppMember({ page }, use) {
13
13
  await use(async (role) => {
14
- const btn = page.getByTestId('create-account');
15
- if (await btn.isVisible()) {
16
- await page.getByTestId('app-role').selectOption(role);
17
- await btn.click();
18
- }
14
+ await page.getByTestId('app-role').selectOption(role);
15
+ await page.getByTestId('create-account').click();
16
+ await expect(page.getByText('Login failed')).toBeHidden();
19
17
  });
20
18
  },
21
19
  });
@@ -1 +1 @@
1
- export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & import("./app/index.js").AppFixtures & import("./app-collection/index.js").AppCollectionFixtures & import("./demo-app/index.js").DemoAppFixtures & import("./group/index.js").GroupFixtures & import("./live-app/index.js").LiveAppFixtures & import("./resource/index.js").ResourceFixtures & import("./studio/index.js").StudioFixtures & import("./training/index.js").TrainingFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
1
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & import("./app/index.js").AppFixtures & import("./app-collection/index.js").AppCollectionFixtures & import("./demo-app/index.js").DemoAppFixtures & import("./group/index.js").GroupFixtures & import("./live-app/index.js").LiveAppFixtures & import("./resource/index.js").ResourceFixtures & import("./studio/index.js").StudioFixtures & import("./test-utils/index.js").TestUtilsFixtures & import("./training/index.js").TrainingFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -6,6 +6,7 @@ import { test as groupFixtures } from './group/index.js';
6
6
  import { test as liveAppFixtures } from './live-app/index.js';
7
7
  import { test as resourceFixtures } from './resource/index.js';
8
8
  import { test as studioFixtures } from './studio/index.js';
9
+ import { test as testUtilsFixtures } from './test-utils/index.js';
9
10
  import { test as trainingFixtures } from './training/index.js';
10
- export const test = mergeTests(appFixtures, appCollectionFixtures, demoAppFixtures, groupFixtures, liveAppFixtures, resourceFixtures, studioFixtures, trainingFixtures);
11
+ export const test = mergeTests(appFixtures, appCollectionFixtures, demoAppFixtures, groupFixtures, liveAppFixtures, resourceFixtures, studioFixtures, testUtilsFixtures, trainingFixtures);
11
12
  //# sourceMappingURL=index.js.map
@@ -9,6 +9,16 @@ export interface LiveAppFixtures {
9
9
  * Login to an app with the Appsemble OAuth2 flow.
10
10
  */
11
11
  loginAppAppsembleOAuth: () => Promise<void>;
12
+ /**
13
+ * Logs into an app using the 'Appsemble Login' flow with the provided App Member credentials
14
+ *
15
+ * > **Warning:** Expects the test to be at the login screen of an app that has the "Display
16
+ * Appsemble login method" option enabled.
17
+ *
18
+ * @param email Email of the App Member to log in as
19
+ * @param password Password of the App Member to log in as
20
+ */
21
+ loginAppAppsembleLogin: (email: string, password: string) => Promise<void>;
12
22
  /**
13
23
  * Gives the app consent to perform actions on the app member's behalf
14
24
  *
@@ -16,9 +16,21 @@ export const test = base.extend({
16
16
  const params = new URLSearchParams(page.url().split('Login?')[1]);
17
17
  const redirect = params.get('redirect');
18
18
  await page.getByTestId('login-with-appsemble').click();
19
+ if (!redirect) {
20
+ return;
21
+ }
19
22
  await page.waitForURL(`**${redirect}`);
20
23
  });
21
24
  },
25
+ async loginAppAppsembleLogin({ page }, use) {
26
+ await use(async (email, password) => {
27
+ await page.waitForURL('**/Login**');
28
+ await page.getByTestId('email').fill(email);
29
+ await page.getByTestId('password').fill(password);
30
+ await page.getByTestId('login').click();
31
+ await expect(page.getByText('Login failed')).toBeHidden();
32
+ });
33
+ },
22
34
  async giveAppConsent({ getLiveAppURL, request }, use) {
23
35
  await use(async (appId) => {
24
36
  const url = await getLiveAppURL(appId);
@@ -1,13 +1,15 @@
1
1
  import { type Resource } from '@appsemble/types';
2
2
  export interface ResourceFixtures {
3
3
  /**
4
- * Create resource(s) from JSON input.
4
+ * Create resource from JSON input.
5
5
  *
6
6
  * @param appId The ID of the app.
7
7
  * @param type The resource type.
8
8
  * @param input The resource JSON input.
9
+ * @param assets Any assets that need to be linked to a field
10
+ * @returns The created resource
9
11
  */
10
- createResource: (appId: number, type: string, input: unknown) => Promise<Resource>;
12
+ createResource: (appId: number, type: string, input: unknown, assets?: Blob[]) => Promise<Resource>;
11
13
  /**
12
14
  * Delete all resources from an app.
13
15
  *
@@ -2,9 +2,16 @@ import { test as base } from '@playwright/test';
2
2
  import { expect } from '../../expect/index.js';
3
3
  export const test = base.extend({
4
4
  async createResource({ request }, use) {
5
- await use(async (appId, type, input) => {
5
+ await use(async (appId, type, input, assets) => {
6
+ const formData = new FormData();
7
+ formData.append('resource', JSON.stringify(input));
8
+ if (assets) {
9
+ for (const asset of assets) {
10
+ formData.append('assets', asset);
11
+ }
12
+ }
6
13
  const response = await request.post(`/api/apps/${appId}/resources/${type}`, {
7
- data: JSON.stringify(input),
14
+ multipart: formData,
8
15
  headers: {
9
16
  Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
10
17
  },
@@ -0,0 +1,10 @@
1
+ export interface TestUtilsFixtures {
2
+ /**
3
+ * Generates a random test ID based on details of the current test
4
+ *
5
+ * @returns Randomized test ID
6
+ * @example '[chrome][index=1][retry=3]-123456'
7
+ */
8
+ randomTestId: () => string;
9
+ }
10
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & TestUtilsFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -0,0 +1,7 @@
1
+ import { test as base } from '@playwright/test';
2
+ export const test = base.extend({
3
+ randomTestId({ browserName }, use, testInfo) {
4
+ use(() => `[${browserName}][index=${testInfo.workerIndex}][retry=${testInfo.retry}]-${Math.floor(100000 + Math.random() * 900000)}`);
5
+ },
6
+ });
7
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/e2e",
3
- "version": "0.34.2",
3
+ "version": "0.34.5-test.0",
4
4
  "description": "End-to-end tests used for testing Appsemble apps in an existing environment.",
5
5
  "keywords": [
6
6
  "app",
@@ -38,9 +38,9 @@
38
38
  "test": "vitest"
39
39
  },
40
40
  "dependencies": {
41
- "@appsemble/utils": "0.34.2",
42
- "@appsemble/types": "0.34.2",
43
- "@appsemble/lang-sdk": "0.34.2",
41
+ "@appsemble/utils": "0.34.5-test.0",
42
+ "@appsemble/types": "0.34.5-test.0",
43
+ "@appsemble/lang-sdk": "0.34.5-test.0",
44
44
  "jwt-decode": "^4.0.0",
45
45
  "@fortawesome/fontawesome-free": "^6.0.0",
46
46
  "@playwright/test": "1.51.1",