@appsemble/e2e 0.32.2

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.
Files changed (76) hide show
  1. package/LICENSE.md +126 -0
  2. package/README.md +64 -0
  3. package/fixtures/expect/index.d.ts +5 -0
  4. package/fixtures/expect/index.js +4 -0
  5. package/fixtures/expect/training/index.d.ts +17 -0
  6. package/fixtures/expect/training/index.js +30 -0
  7. package/fixtures/index.d.ts +2 -0
  8. package/fixtures/index.js +3 -0
  9. package/fixtures/test/app/index.d.ts +24 -0
  10. package/fixtures/test/app/index.js +40 -0
  11. package/fixtures/test/app-collection/index.d.ts +22 -0
  12. package/fixtures/test/app-collection/index.js +42 -0
  13. package/fixtures/test/demo-app/index.d.ts +15 -0
  14. package/fixtures/test/demo-app/index.js +22 -0
  15. package/fixtures/test/group/index.d.ts +11 -0
  16. package/fixtures/test/group/index.js +13 -0
  17. package/fixtures/test/index.d.ts +1 -0
  18. package/fixtures/test/index.js +11 -0
  19. package/fixtures/test/live-app/index.d.ts +27 -0
  20. package/fixtures/test/live-app/index.js +50 -0
  21. package/fixtures/test/resource/index.d.ts +18 -0
  22. package/fixtures/test/resource/index.js +29 -0
  23. package/fixtures/test/studio/index.d.ts +17 -0
  24. package/fixtures/test/studio/index.js +33 -0
  25. package/fixtures/test/training/index.d.ts +7 -0
  26. package/fixtures/test/training/index.js +18 -0
  27. package/index.d.ts +2 -0
  28. package/index.js +3 -0
  29. package/package.json +52 -0
  30. package/playwright.config.d.ts +5 -0
  31. package/playwright.config.js +48 -0
  32. package/setup/auth.setup.d.ts +1 -0
  33. package/setup/auth.setup.js +10 -0
  34. package/tests/studio/app-collections/app-collections.spec.d.ts +1 -0
  35. package/tests/studio/app-collections/app-collections.spec.js +158 -0
  36. package/tests/studio/apps/app.spec.d.ts +1 -0
  37. package/tests/studio/apps/app.spec.js +79 -0
  38. package/tests/studio/apps.spec.d.ts +1 -0
  39. package/tests/studio/apps.spec.js +30 -0
  40. package/tests/studio/blocks.spec.d.ts +1 -0
  41. package/tests/studio/blocks.spec.js +20 -0
  42. package/tests/studio/docs.spec.d.ts +1 -0
  43. package/tests/studio/docs.spec.js +28 -0
  44. package/tests/studio/logins/appsemble.spec.d.ts +1 -0
  45. package/tests/studio/logins/appsemble.spec.js +14 -0
  46. package/tests/studio/logins/demo.spec.d.ts +1 -0
  47. package/tests/studio/logins/demo.spec.js +9 -0
  48. package/tests/studio/logins/invite.spec.d.ts +1 -0
  49. package/tests/studio/logins/invite.spec.js +12 -0
  50. package/tests/studio/logins/oauth2.spec.d.ts +1 -0
  51. package/tests/studio/logins/oauth2.spec.js +56 -0
  52. package/tests/studio/logins/password.spec.d.ts +1 -0
  53. package/tests/studio/logins/password.spec.js +14 -0
  54. package/tests/studio/logins/register.spec.d.ts +1 -0
  55. package/tests/studio/logins/register.spec.js +14 -0
  56. package/tests/studio/logins/saml.spec.d.ts +1 -0
  57. package/tests/studio/logins/saml.spec.js +14 -0
  58. package/tests/studio/organizations.spec.d.ts +1 -0
  59. package/tests/studio/organizations.spec.js +33 -0
  60. package/tests/studio/root.spec.d.ts +1 -0
  61. package/tests/studio/root.spec.js +8 -0
  62. package/tests/studio/trainings/constants.d.ts +4 -0
  63. package/tests/studio/trainings/constants.js +5 -0
  64. package/tests/studio/trainings/logged-in.spec.d.ts +1 -0
  65. package/tests/studio/trainings/logged-in.spec.js +43 -0
  66. package/tests/studio/trainings/logged-out.spec.d.ts +1 -0
  67. package/tests/studio/trainings/logged-out.spec.js +42 -0
  68. package/tests/template-apps/empty.spec.d.ts +1 -0
  69. package/tests/template-apps/empty.spec.js +14 -0
  70. package/tests/template-apps/holidays.spec.d.ts +1 -0
  71. package/tests/template-apps/holidays.spec.js +25 -0
  72. package/tests/template-apps/notes.spec.d.ts +1 -0
  73. package/tests/template-apps/notes.spec.js +25 -0
  74. package/tests/template-apps/person.spec.d.ts +1 -0
  75. package/tests/template-apps/person.spec.js +25 -0
  76. package/vitest.config.js +3 -0
@@ -0,0 +1,33 @@
1
+ import { test as base, expect } from '@playwright/test';
2
+ export const test = base.extend({
3
+ async loginUser({ page }, use) {
4
+ await use(async (email, password, saveInStorageState = false) => {
5
+ let accessToken = '';
6
+ // Get the access token from the "login" response
7
+ page.on('response', async (response) => {
8
+ if (!response.url().endsWith('/api/auth/email/login')) {
9
+ return;
10
+ }
11
+ accessToken = (await response.json()).access_token;
12
+ expect(accessToken).toStrictEqual(expect.any(String));
13
+ });
14
+ await page.goto('/en/login');
15
+ await page.getByTestId('email').fill(email);
16
+ await page.getByTestId('password').fill(password);
17
+ await page.getByTestId('login').click();
18
+ await page.waitForResponse('**/api/auth/email/login');
19
+ await expect(page.getByText('Login failed')).toBeHidden();
20
+ if (saveInStorageState) {
21
+ await page.context().storageState({ path: '.auth/user.json' });
22
+ }
23
+ return accessToken;
24
+ });
25
+ },
26
+ async logoutUser({ page }, use) {
27
+ await use(async () => {
28
+ await page.getByRole('button').nth(1).click();
29
+ await page.getByRole('button', { name: 'Logout' }).click();
30
+ });
31
+ },
32
+ });
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ export interface TrainingFixtures {
2
+ /**
3
+ * Resets the user's training progress
4
+ */
5
+ resetTrainingProgress: () => Promise<void>;
6
+ }
7
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & TrainingFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -0,0 +1,18 @@
1
+ import { test as base, expect } from '@playwright/test';
2
+ export const test = base.extend({
3
+ async resetTrainingProgress({ baseURL, context }, use) {
4
+ await use(async () => {
5
+ const accessToken = (await context.storageState()).origins[0].localStorage[0].value;
6
+ expect(accessToken).not.toBeNull();
7
+ const { status } = await fetch(`${baseURL}/api/trainings/completed`, {
8
+ method: 'DELETE',
9
+ headers: {
10
+ Authorization: `Bearer ${accessToken}`,
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ });
14
+ expect(status).toBe(204);
15
+ });
16
+ },
17
+ });
18
+ //# sourceMappingURL=index.js.map
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { test } from './fixtures/test/index.js';
2
+ export { expect } from './fixtures/expect/index.js';
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { test } from './fixtures/test/index.js';
2
+ export { expect } from './fixtures/expect/index.js';
3
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@appsemble/e2e",
3
+ "version": "0.32.2",
4
+ "description": "End-to-end tests used for testing Appsemble apps in an existing environment.",
5
+ "keywords": [
6
+ "app",
7
+ "apps",
8
+ "appsemble",
9
+ "e2e",
10
+ "framework",
11
+ "low-code",
12
+ "lowcode"
13
+ ],
14
+ "homepage": "https://appsemble.app",
15
+ "bugs": "https://gitlab.com/appsemble/appsemble/-/issues",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://gitlab.com/appsemble/appsemble.git",
19
+ "directory": "packages/e2e"
20
+ },
21
+ "license": "LGPL-3.0-only",
22
+ "author": "Appsemble <info@appsemble.com> (https://appsemble.com)",
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "ts-source": "./index.ts",
27
+ "default": "./index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "**/*.d.ts",
32
+ "**/*.js"
33
+ ],
34
+ "scripts": {
35
+ "prepack": "tsc --noEmit false",
36
+ "e2e": "playwright test",
37
+ "codegen": "playwright codegen",
38
+ "test": "vitest"
39
+ },
40
+ "dependencies": {
41
+ "@appsemble/utils": "0.32.2",
42
+ "@appsemble/types": "0.32.2",
43
+ "jwt-decode": "^4.0.0",
44
+ "@fortawesome/fontawesome-free": "^6.0.0",
45
+ "@playwright/test": "1.51.1",
46
+ "bulma": "=0.9.3",
47
+ "strip-indent": "^4.0.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=20"
51
+ }
52
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * See https://playwright.dev/docs/test-configuration.
3
+ */
4
+ declare const _default: import("@playwright/test").PlaywrightTestConfig<{}, {}>;
5
+ export default _default;
@@ -0,0 +1,48 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+ const { CI, CI_MERGE_REQUEST_IID, E2E_HOST } = process.env;
3
+ /**
4
+ * See https://playwright.dev/docs/test-configuration.
5
+ */
6
+ export default defineConfig({
7
+ testMatch: '**/*.spec.ts',
8
+ // Tests aren't fully isolated so they shouldn't run in parallel
9
+ fullyParallel: false,
10
+ // Fail the build on CI if you accidentally left test.only in the source code.
11
+ forbidOnly: Boolean(CI),
12
+ // Retry on CI only
13
+ retries: CI ? 2 : 0,
14
+ // A lot of tests depend on the same pre-seeded apps, so more workers can introduce flakiness.
15
+ workers: 1,
16
+ // Reporter to use. See https://playwright.dev/docs/test-reporters
17
+ reporter: [['junit', { outputFile: 'results.xml' }]],
18
+ // Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions.
19
+ use: {
20
+ // Base URL to use in actions like `await page.goto('/')`.
21
+ baseURL: CI
22
+ ? (E2E_HOST ?? `https://${CI_MERGE_REQUEST_IID || 'staging'}.appsemble.review`)
23
+ : 'http://localhost:9999',
24
+ // Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer
25
+ trace: 'on-first-retry',
26
+ video: 'on',
27
+ },
28
+ // Configure projects for major browsers
29
+ projects: [
30
+ { name: 'setup', testMatch: /.*\.setup\.ts/ },
31
+ {
32
+ name: 'chromium',
33
+ use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' },
34
+ dependencies: ['setup'],
35
+ },
36
+ {
37
+ name: 'firefox',
38
+ use: { ...devices['Desktop Firefox'], storageState: '.auth/user.json' },
39
+ dependencies: ['setup'],
40
+ },
41
+ {
42
+ name: 'webkit',
43
+ use: { ...devices['Desktop Safari'], storageState: '.auth/user.json' },
44
+ dependencies: ['setup'],
45
+ },
46
+ ],
47
+ });
48
+ //# sourceMappingURL=playwright.config.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { expect, test as setup } from '../index.js';
2
+ const { BOT_ACCOUNT_EMAIL, BOT_ACCOUNT_PASSWORD } = process.env;
3
+ setup('authenticate', async ({ loginUser }) => {
4
+ expect(BOT_ACCOUNT_EMAIL && BOT_ACCOUNT_PASSWORD).not.toBeUndefined();
5
+ const accessToken = await loginUser(BOT_ACCOUNT_EMAIL, BOT_ACCOUNT_PASSWORD, true);
6
+ expect(accessToken).toStrictEqual(expect.any(String));
7
+ // Playwright uses this environment variable to authenticate API requests
8
+ process.env.ACCESS_TOKEN = accessToken;
9
+ });
10
+ //# sourceMappingURL=auth.setup.js.map
@@ -0,0 +1,158 @@
1
+ import { expect, test } from '../../../index.js';
2
+ const organizationId = 'appsemble';
3
+ let appCollectionName;
4
+ test.describe('App Collections', () => {
5
+ test.beforeEach(async ({ page }) => {
6
+ await page.goto(`/en/organizations/${organizationId}/collections`);
7
+ });
8
+ test.afterEach(async ({ deleteAppCollection }) => {
9
+ if (appCollectionName !== undefined) {
10
+ await deleteAppCollection(organizationId, appCollectionName);
11
+ }
12
+ });
13
+ test('should create an app collection', async ({ browserName, createAppCollection, page }) => {
14
+ appCollectionName = `Create test ${browserName.slice(0, 8)}`;
15
+ await page.route('/test-assets/side.png', (route) => {
16
+ route.fulfill({ path: 'mock-data/tux.png' });
17
+ });
18
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum\n\n**text in bold**\n\n![side image](/test-assets/side.png)', 'mock-data/retail.png', 'mock-data/tux.png', false);
19
+ await page.getByRole('link', { name: appCollectionName }).click();
20
+ await expect(page.getByLabel('Breadcrumbs').locator('li:nth-child(2)')).toHaveText(appCollectionName);
21
+ await expect(page.locator('header.has-background-primary.is-flex.is-flex-wrap-wrap')).toHaveScreenshot({
22
+ timeout: 5000,
23
+ maxDiffPixelRatio: 0.02,
24
+ });
25
+ await page.getByRole('link', { name: 'Expert' }).click();
26
+ await page.getByRole('img', { name: 'side image' }).waitFor({ state: 'visible' });
27
+ // TODO: Make sure this doesn't die in CI
28
+ await expect(page.locator('section:has(div)')).toHaveScreenshot({
29
+ timeout: 5000,
30
+ maxDiffPixelRatio: 0.02,
31
+ });
32
+ });
33
+ test('should create a private app collection', async ({ browser, browserName, createAppCollection, page, }) => {
34
+ appCollectionName = `Private test ${browserName.slice(0, 8)}`;
35
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', true);
36
+ await page.goto('/en/collections');
37
+ await page.getByRole('heading', { name: 'App collections' }).waitFor({ state: 'visible' });
38
+ await expect(page.getByRole('link', { name: appCollectionName })).toBeVisible({
39
+ timeout: 5000,
40
+ });
41
+ const loggedOutPage = await browser.newPage({ storageState: undefined });
42
+ await loggedOutPage.goto('/en/collections');
43
+ await loggedOutPage
44
+ .getByRole('heading', { name: 'App collections' })
45
+ .waitFor({ state: 'visible' });
46
+ await expect(loggedOutPage.getByRole('link', { name: appCollectionName })).toBeHidden({
47
+ timeout: 5000,
48
+ });
49
+ });
50
+ test('should edit an app collection', async ({ browserName, createAppCollection, page }) => {
51
+ appCollectionName = `Edit test ${browserName.slice(0, 8)}`;
52
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
53
+ await page.getByRole('link', { name: appCollectionName }).click();
54
+ await page.getByRole('link', { name: 'Settings' }).click();
55
+ const oldName = appCollectionName;
56
+ appCollectionName = appCollectionName.split('').reverse().join('');
57
+ await page.getByLabel('Name', { exact: true }).fill(appCollectionName);
58
+ await page.getByLabel('Expert name').fill('Jane Doe');
59
+ await page.getByLabel('Expert description').fill('Hello world');
60
+ await page.getByRole('button', { name: 'Save' }).click();
61
+ await page.getByRole('link', { name: /Apps$/ }).click();
62
+ await expect(page.getByRole('heading', { name: oldName })).toBeHidden();
63
+ await expect(page.getByRole('heading', { name: appCollectionName })).toBeVisible();
64
+ await page.getByRole('link', { name: 'Expert Jane Doe' }).click();
65
+ await expect(page.locator('h1').filter({ hasText: 'Jane Doe' })).toBeVisible();
66
+ });
67
+ test('should delete an app collection', async ({ browserName, createAppCollection, page }) => {
68
+ appCollectionName = `Delete test ${browserName.slice(0, 8)}`;
69
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
70
+ await page.getByRole('link', { name: `${appCollectionName} John Doe` }).click();
71
+ await page.getByRole('link', { name: 'Settings' }).click();
72
+ await page.getByRole('button', { name: 'Delete app collection' }).click();
73
+ await page.getByRole('button', { name: 'Delete app collection', exact: true }).click();
74
+ await page.getByRole('heading', { name: 'App collections' }).waitFor({ state: 'visible' });
75
+ await expect(page.getByRole('link', { name: appCollectionName })).toBeHidden({
76
+ timeout: 5000,
77
+ });
78
+ // Set to undefined so the afterEach doesn't try to delete a non-existent collection
79
+ appCollectionName = undefined;
80
+ });
81
+ test('should add an app to an app collection', async ({ browserName, createAppCollection, page, }) => {
82
+ appCollectionName = `Add app test ${browserName.slice(0, 8)}`;
83
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
84
+ await page.getByRole('link', { name: 'App store' }).click();
85
+ await page.getByRole('link', { name: 'Empty' }).first().click();
86
+ await page.click('#app-options-menu');
87
+ await page.getByRole('button', { name: 'Add to collection' }).click();
88
+ await page.locator('#collectionId').selectOption({ label: appCollectionName });
89
+ await page
90
+ .locator('.modal-card-foot')
91
+ .getByRole('button', { name: 'Add to collection' })
92
+ .click();
93
+ await page.goto('/en/collections');
94
+ await page.getByRole('link', { name: appCollectionName }).click();
95
+ await expect(page.getByRole('link', { name: 'Empty' })).toBeVisible();
96
+ });
97
+ test('should remove an app from an app collection', async ({ browserName, createAppCollection, page, }) => {
98
+ appCollectionName = `Rm app test ${browserName.slice(0, 8)}`;
99
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
100
+ await page.getByRole('link', { name: 'App store' }).click();
101
+ await page.getByRole('link', { name: 'Empty' }).first().click();
102
+ await page.click('#app-options-menu');
103
+ await page.getByRole('button', { name: 'Add to collection' }).click();
104
+ await page.locator('#collectionId').selectOption({ label: appCollectionName });
105
+ await page
106
+ .locator('.modal-card-foot')
107
+ .getByRole('button', { name: 'Add to collection' })
108
+ .click();
109
+ await page.goto('/en/collections');
110
+ await page.getByRole('link', { name: appCollectionName }).click();
111
+ await page.getByRole('button', { name: 'Edit' }).click();
112
+ await page
113
+ .locator('div', { has: page.getByRole('link', { name: 'Empty' }) })
114
+ .locator('button[title="Delete app from collection"]')
115
+ .click();
116
+ await page.getByRole('button', { name: 'Confirm' }).click();
117
+ await expect(page.getByRole('link', { name: 'Empty' })).toBeHidden();
118
+ });
119
+ test('should pin an app to an app collection', async ({ browserName, createAppCollection, page, }) => {
120
+ appCollectionName = `Pin app test ${browserName.slice(0, 8)}`;
121
+ await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
122
+ await page.getByRole('link', { name: 'App store' }).click();
123
+ await page.getByRole('link', { name: 'Empty' }).first().click();
124
+ await page.click('#app-options-menu');
125
+ await page.getByRole('button', { name: 'Add to collection' }).click();
126
+ await page.locator('#collectionId').selectOption({ label: appCollectionName });
127
+ await page
128
+ .locator('.modal-card-foot')
129
+ .getByRole('button', { name: 'Add to collection' })
130
+ .click();
131
+ await page.goBack();
132
+ await page.getByRole('link', { name: 'Person' }).first().click();
133
+ await page.click('#app-options-menu');
134
+ await page.getByRole('button', { name: 'Add to collection' }).click();
135
+ await page.locator('#collectionId').selectOption({ label: appCollectionName });
136
+ await page
137
+ .locator('.modal-card-foot')
138
+ .getByRole('button', { name: 'Add to collection' })
139
+ .click();
140
+ await page.goto('/en/collections');
141
+ await page.getByRole('link', { name: appCollectionName }).click();
142
+ await page.getByRole('button', { name: 'Edit' }).click();
143
+ await page
144
+ .locator('div', {
145
+ has: page.getByRole('link', { name: 'Person' }),
146
+ hasNot: page.getByRole('link', { name: 'Empty' }),
147
+ })
148
+ .locator('button[title="Pin app to top of collection"]')
149
+ .click();
150
+ await page.getByRole('button', { name: 'Stop editing' }).click();
151
+ // TODO make sure this doesn't die in CI either
152
+ await expect(page.getByRole('main')).toHaveScreenshot({
153
+ timeout: 5000,
154
+ maxDiffPixelRatio: 0.02,
155
+ });
156
+ });
157
+ });
158
+ //# sourceMappingURL=app-collections.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { expect, test } from '../../../index.js';
2
+ async function clickSideMenuItem(page, title) {
3
+ await page.getByTestId('studio-app-side-menu').getByRole('link', { name: title }).click();
4
+ }
5
+ test.describe('/apps/:appId', () => {
6
+ test.beforeEach(async ({ page }) => {
7
+ await page.goto('/en/apps');
8
+ await page.getByRole('link', { name: 'Person Appsemble' }).first().click();
9
+ await expect(page.getByTestId('studio-app-side-menu-name')).toHaveText('Person');
10
+ });
11
+ test('should prompt when user has unsaved changes', async ({ page }) => {
12
+ await clickSideMenuItem(page, 'Editor');
13
+ page.on('dialog', (alert) => {
14
+ expect(alert.type()).toBe('confirm');
15
+ expect(alert.message()).toBe('You have unsaved changes. Do you wish to continue?');
16
+ alert.dismiss();
17
+ });
18
+ await expect(page.getByRole('button', { name: 'Publish' })).toBeDisabled();
19
+ await page.getByRole('code').getByText('Person', { exact: true }).dblclick();
20
+ await page.getByRole('code').getByText('Person', { exact: true }).press('Backspace');
21
+ await page.getByRole('code').getByText('name:').first().press('t+e+s+t');
22
+ await expect(page.getByRole('button', { name: 'Publish' })).toBeEnabled();
23
+ await page.getByRole('link', { name: 'Details' }).click();
24
+ await expect(page.getByText('Clone App')).toBeHidden();
25
+ });
26
+ test('should not prompt when user has saved their changes', async ({ page }) => {
27
+ await clickSideMenuItem(page, 'Editor');
28
+ await page.route('/api/apps/*', (route) => {
29
+ route.fulfill({ path: 'mock-data/person-app-definition.json' });
30
+ });
31
+ page.on('dialog', (alert) => {
32
+ // If a dialog comes up at all, the test should fail
33
+ expect(alert).toBeNull();
34
+ });
35
+ await expect(page.getByRole('button', { name: 'Publish' })).toBeDisabled();
36
+ await page.getByRole('code').getByText('Person', { exact: true }).dblclick();
37
+ await page.getByRole('code').getByText('Person', { exact: true }).press('Backspace');
38
+ await page.getByRole('code').getByText('name:').first().press('P+e+r+s+o+n');
39
+ await page.getByRole('button', { name: 'Publish' }).click();
40
+ await expect(page.getByText('Successfully updated app definition')).toBeVisible();
41
+ await page.getByRole('link', { name: 'Details' }).click();
42
+ await expect(page.getByText('Clone App')).toBeVisible();
43
+ });
44
+ test('should link to the asset viewer', async ({ page }) => {
45
+ await clickSideMenuItem(page, 'Assets');
46
+ await expect(page.getByText('Upload new asset')).toBeVisible();
47
+ });
48
+ test('should link to resources', async ({ page }) => {
49
+ await clickSideMenuItem(page, 'Resources');
50
+ await expect(page.getByText('This app has the following resources')).toBeVisible();
51
+ });
52
+ test('should link to a specific resource', async ({ page }) => {
53
+ await clickSideMenuItem(page, 'Resources');
54
+ await page.click('li :has-text("person")');
55
+ await expect(page.getByText('Resource: person')).toBeVisible();
56
+ });
57
+ test('should link to the translator tool', async ({ page }) => {
58
+ await clickSideMenuItem(page, 'Translations');
59
+ await expect(page.getByText('Selected language')).toBeVisible();
60
+ });
61
+ test('should link to the notification sender', async ({ page }) => {
62
+ await clickSideMenuItem(page, 'Notifications');
63
+ await expect(page.getByText('Push notifications are currently not enabled in this app.')).toBeVisible();
64
+ });
65
+ test('should link to the snapshots page', async ({ page }) => {
66
+ await clickSideMenuItem(page, 'Snapshots');
67
+ await expect(page.getByRole('heading', { name: 'Snapshots' })).toBeVisible();
68
+ await expect(page.getByRole('listitem').filter({ has: page.getByRole('heading', { name: 'Appsemble' }) })).toBeVisible();
69
+ });
70
+ test('should link to the app settings', async ({ page }) => {
71
+ await clickSideMenuItem(page, 'Settings');
72
+ await expect(page.getByRole('heading', { name: 'App lock' })).toBeVisible();
73
+ });
74
+ test('should link to the app secrets', async ({ page }) => {
75
+ await clickSideMenuItem(page, 'Secrets');
76
+ await expect(page.getByRole('heading', { name: 'Appsemble Login' })).toBeVisible();
77
+ });
78
+ });
79
+ //# sourceMappingURL=app.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { expect, test } from '../../index.js';
2
+ test.describe('Apps', () => {
3
+ test.beforeEach(async ({ page }) => {
4
+ await page.goto('/en/apps');
5
+ });
6
+ test('should show the page header', async ({ page }) => {
7
+ await expect(page.locator('text=All Apps')).toBeVisible();
8
+ });
9
+ test('should display “My Apps” when logged in', async ({ page }) => {
10
+ await expect(page.getByText('My Apps')).toBeVisible();
11
+ });
12
+ test('should render default apps', async ({ page }) => {
13
+ const defaultApps = [
14
+ { name: 'Empty App' },
15
+ { name: 'Holidays' },
16
+ { name: 'Notes' },
17
+ { name: 'Person' },
18
+ { name: 'Survey' },
19
+ { name: 'Unlittered' },
20
+ ];
21
+ for (const { name } of defaultApps) {
22
+ await expect(page.getByText(name).first()).toBeVisible();
23
+ }
24
+ });
25
+ test('should link to app details', async ({ page }) => {
26
+ await page.click('text=Empty App');
27
+ await expect(page.getByText('Empty App is a bare-bones app with two pages and buttons switching between them.')).toBeVisible();
28
+ });
29
+ });
30
+ //# sourceMappingURL=apps.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { expect, test } from '../../index.js';
2
+ test.describe('Blocks', () => {
3
+ test.beforeEach(async ({ page }) => {
4
+ await page.goto('/en/blocks');
5
+ });
6
+ test('should render a list of blocks', async ({ page }) => {
7
+ const dataLoader = page.locator('[title="@appsemble/data-loader"]');
8
+ await expect(dataLoader).toContainText('data-loader');
9
+ await expect(dataLoader).toContainText('@appsemble');
10
+ await expect(dataLoader).toContainText('A block that fetches data and emits it using the events API.');
11
+ });
12
+ test('should link to block details', async ({ page }) => {
13
+ const dataLoader = page.locator('[title="@appsemble/data-loader"]');
14
+ await dataLoader.locator('text=View details').click();
15
+ await expect(page.getByRole('heading', { name: 'Parameters', exact: true })).toBeVisible();
16
+ await expect(page.getByRole('heading', { name: 'Actions', exact: true })).toBeVisible();
17
+ await expect(page.getByRole('heading', { name: 'Events', exact: true })).toBeVisible();
18
+ });
19
+ });
20
+ //# sourceMappingURL=blocks.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { AppPermission, OrganizationPermission, predefinedAppRoles, predefinedOrganizationRoles, } from '@appsemble/types';
2
+ import { expect, test } from '../../index.js';
3
+ test.describe('Docs', () => {
4
+ test('should render the index page', async ({ page }) => {
5
+ await page.goto('/en/docs');
6
+ await expect(page.locator('.menu-list').getByRole('link', { name: 'What is Appsemble?' })).toBeVisible();
7
+ });
8
+ test('should render the permissions table', async ({ page }) => {
9
+ await page.goto('/en/docs/studio/organizations');
10
+ await expect(page.getByRole('row')).toHaveText([
11
+ ['Permissions', ...predefinedOrganizationRoles].join(''),
12
+ ...Object.values(OrganizationPermission)
13
+ .filter((permission) => typeof permission === 'string')
14
+ .map((permission) => new RegExp(String(permission))),
15
+ ]);
16
+ });
17
+ test('should render the app member permissions table', async ({ page }) => {
18
+ await page.goto('/en/docs/app/security');
19
+ await expect(page.getByRole('row')).toHaveText([
20
+ ['Permissions', ...predefinedAppRoles].join(''),
21
+ ...Object.values(AppPermission)
22
+ .filter((permission) => !permission.startsWith('$resource:all:own'))
23
+ .filter((permission) => typeof permission === 'string')
24
+ .map((permission) => new RegExp(`\\${String(permission)}`)),
25
+ ]);
26
+ });
27
+ });
28
+ //# sourceMappingURL=docs.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { expect, test } from '../../../index.js';
2
+ test.describe('Appsemble OAuth2 app login', () => {
3
+ test.fixme('should work', async ({ page }) => {
4
+ await page.getByRole('link', { name: 'Login' }).click();
5
+ await page.getByRole('button', { name: 'Login with Appsemble' }).click();
6
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
7
+ });
8
+ test.fixme('should handle app member email conflict', async ({ page }) => {
9
+ await page.getByRole('link', { name: 'Login' }).click();
10
+ await page.getByRole('button', { name: 'Login with Appsemble' }).click();
11
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
12
+ });
13
+ });
14
+ //# sourceMappingURL=appsemble.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { expect, test } from '../../../index.js';
2
+ test.describe('Demo login', () => {
3
+ test.fixme('should work', async ({ page }) => {
4
+ await page.getByRole('link', { name: 'Login' }).click();
5
+ await page.getByRole('button', { name: 'Login with ...' }).click();
6
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
7
+ });
8
+ });
9
+ //# sourceMappingURL=demo.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { expect, test } from '../../../index.js';
2
+ test.describe('App invite', () => {
3
+ test.fixme('should work', async ({ page }) => {
4
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
5
+ });
6
+ test.fixme('should handle app member email conflict', async ({ page }) => {
7
+ await page.getByRole('link', { name: 'Login' }).click();
8
+ await page.getByRole('button', { name: 'Login with Appsemble' }).click();
9
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
10
+ });
11
+ });
12
+ //# sourceMappingURL=invite.spec.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { expect, test } from '../../../index.js';
2
+ let app;
3
+ let secret;
4
+ test.describe('App OAuth2 login', () => {
5
+ test.beforeAll(async ({ createApp, request }) => {
6
+ app = await createApp('appsemble', `
7
+ name: Test App
8
+ defaultPage: Test Page
9
+ security:
10
+ default:
11
+ role: test
12
+ policy: everyone
13
+ roles:
14
+ test:
15
+ description: test
16
+ pages:
17
+ - name: Test Page
18
+ blocks:
19
+ - type: test
20
+ version: 0.0.0
21
+ `);
22
+ secret = (await (await request.post(`/api/apps/${app.id}/secrets/oauth2`, {
23
+ data: {
24
+ name: 'OAuth2',
25
+ icon: 'test',
26
+ authorizationUrl: 'https://example.com/oauth2/authorize',
27
+ tokenUrl: 'https://example.com/oauth/token',
28
+ clientId: 'example_client_id',
29
+ clientSecret: 'example_client_secret',
30
+ userInfoUrl: 'https://example.com/oauth/userinfo',
31
+ scope: 'email openid profile',
32
+ },
33
+ })).json());
34
+ });
35
+ test.fixme('should work', async ({ page }) => {
36
+ await page.getByRole('link', { name: 'Login' }).click();
37
+ await page.getByRole('button', { name: `Login with ${secret.name}` }).click();
38
+ const redirectUri = 'http://localhost:9999/callback&response_type=code&scope=email openid profile';
39
+ await page.waitForRequest(`/connect/authorize/oauth2/${app.id}?client_id=app:${app.id}&redirect_uri=*&state=*`);
40
+ await page.route(`${secret.authorizationUrl}?client_id=${secret.clientId}&redirect_uri=${redirectUri}&state=*`, (route) => {
41
+ route.fulfill({
42
+ status: 302,
43
+ headers: {
44
+ location: 'http://localhost:9999/callback?code=*&state=*',
45
+ },
46
+ });
47
+ }, { times: 1 });
48
+ await page.waitForRequest('http://test.appsemble.localhost:9999/Callback?code=*&state=*');
49
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
50
+ });
51
+ test.fixme('should handle app member email conflict', async ({ page }) => {
52
+ await page.click('a.button');
53
+ await expect(page.getByRole('heading', { name: 'Example Page A' })).toBeVisible();
54
+ });
55
+ });
56
+ //# sourceMappingURL=oauth2.spec.js.map