samaro-uitest 1.0.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/LICENSE +21 -0
- package/README.md +336 -0
- package/bin/samaro-allure.js +172 -0
- package/bin/samaro-init.js +278 -0
- package/bin/samaro-test.js +36 -0
- package/config/index.d.ts +4 -0
- package/config/index.d.ts.map +1 -0
- package/config/index.js +11 -0
- package/config/index.js.map +1 -0
- package/config/index.ts +3 -0
- package/config/playwright.component.config.d.ts +12 -0
- package/config/playwright.component.config.d.ts.map +1 -0
- package/config/playwright.component.config.js +42 -0
- package/config/playwright.component.config.js.map +1 -0
- package/config/playwright.component.config.ts +60 -0
- package/config/playwright.config.d.ts +14 -0
- package/config/playwright.config.d.ts.map +1 -0
- package/config/playwright.config.js +53 -0
- package/config/playwright.config.js.map +1 -0
- package/config/playwright.config.ts +76 -0
- package/config/test-setup.ts +17 -0
- package/config/vitest.config.d.ts +13 -0
- package/config/vitest.config.d.ts.map +1 -0
- package/config/vitest.config.js +53 -0
- package/config/vitest.config.js.map +1 -0
- package/config/vitest.config.ts +64 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/playwright.component.config.d.ts +12 -0
- package/dist/config/playwright.component.config.d.ts.map +1 -0
- package/dist/config/playwright.component.config.js +39 -0
- package/dist/config/playwright.component.config.js.map +1 -0
- package/dist/config/playwright.config.d.ts +14 -0
- package/dist/config/playwright.config.d.ts.map +1 -0
- package/dist/config/playwright.config.js +50 -0
- package/dist/config/playwright.config.js.map +1 -0
- package/dist/config/test-setup.d.ts +3 -0
- package/dist/config/test-setup.d.ts.map +1 -0
- package/dist/config/test-setup.js +17 -0
- package/dist/config/test-setup.js.map +1 -0
- package/dist/config/vitest.config.d.ts +13 -0
- package/dist/config/vitest.config.d.ts.map +1 -0
- package/dist/config/vitest.config.js +46 -0
- package/dist/config/vitest.config.js.map +1 -0
- package/dist/helpers/api.d.ts +69 -0
- package/dist/helpers/api.d.ts.map +1 -0
- package/dist/helpers/api.js +154 -0
- package/dist/helpers/api.js.map +1 -0
- package/dist/helpers/auth.d.ts +35 -0
- package/dist/helpers/auth.d.ts.map +1 -0
- package/dist/helpers/auth.js +96 -0
- package/dist/helpers/auth.js.map +1 -0
- package/dist/helpers/config.d.ts +34 -0
- package/dist/helpers/config.d.ts.map +1 -0
- package/dist/helpers/config.js +63 -0
- package/dist/helpers/config.js.map +1 -0
- package/dist/helpers/debug.d.ts +48 -0
- package/dist/helpers/debug.d.ts.map +1 -0
- package/dist/helpers/debug.js +76 -0
- package/dist/helpers/debug.js.map +1 -0
- package/dist/helpers/index.d.ts +6 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +11 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/setup.d.ts +50 -0
- package/dist/helpers/setup.d.ts.map +1 -0
- package/dist/helpers/setup.js +95 -0
- package/dist/helpers/setup.js.map +1 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/helpers/api.d.ts +69 -0
- package/helpers/api.d.ts.map +1 -0
- package/helpers/api.js +163 -0
- package/helpers/api.js.map +1 -0
- package/helpers/api.ts +224 -0
- package/helpers/auth.d.ts +35 -0
- package/helpers/auth.d.ts.map +1 -0
- package/helpers/auth.js +104 -0
- package/helpers/auth.js.map +1 -0
- package/helpers/auth.ts +125 -0
- package/helpers/config.d.ts +34 -0
- package/helpers/config.d.ts.map +1 -0
- package/helpers/config.js +70 -0
- package/helpers/config.js.map +1 -0
- package/helpers/config.ts +80 -0
- package/helpers/debug.d.ts +48 -0
- package/helpers/debug.d.ts.map +1 -0
- package/helpers/debug.js +86 -0
- package/helpers/debug.js.map +1 -0
- package/helpers/debug.ts +111 -0
- package/helpers/index.d.ts +6 -0
- package/helpers/index.d.ts.map +1 -0
- package/helpers/index.js +47 -0
- package/helpers/index.js.map +1 -0
- package/helpers/index.ts +62 -0
- package/helpers/setup.d.ts +50 -0
- package/helpers/setup.d.ts.map +1 -0
- package/helpers/setup.js +137 -0
- package/helpers/setup.js.map +1 -0
- package/helpers/setup.ts +144 -0
- package/package.json +94 -0
- package/schematics/collection.json +11 -0
- package/schematics/ng-add/index.js +121 -0
- package/schematics/ng-add/schema.json +26 -0
- package/scripts/check-allure-results.js +269 -0
- package/types/allure.d.ts +76 -0
- package/types/global.d.ts +49 -0
package/helpers/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Debug interface helpers
|
|
2
|
+
export {
|
|
3
|
+
setDebugInterfaceName,
|
|
4
|
+
getDebugInterfaceName,
|
|
5
|
+
getToken,
|
|
6
|
+
getAuthState,
|
|
7
|
+
getCurrentUser,
|
|
8
|
+
waitForAuthState,
|
|
9
|
+
isDebugInterfaceAvailable,
|
|
10
|
+
getDebugData,
|
|
11
|
+
type AuthState,
|
|
12
|
+
type User,
|
|
13
|
+
} from './debug';
|
|
14
|
+
|
|
15
|
+
// Authentication helpers
|
|
16
|
+
export {
|
|
17
|
+
register,
|
|
18
|
+
login,
|
|
19
|
+
logout,
|
|
20
|
+
generateUniqueUser,
|
|
21
|
+
isLoggedIn,
|
|
22
|
+
getCurrentUsername,
|
|
23
|
+
type UserCredentials,
|
|
24
|
+
type AuthResult,
|
|
25
|
+
} from './auth';
|
|
26
|
+
|
|
27
|
+
// API helpers
|
|
28
|
+
export {
|
|
29
|
+
getAPIConfig,
|
|
30
|
+
registerUserViaAPI,
|
|
31
|
+
loginUserViaAPI,
|
|
32
|
+
createArticleViaAPI,
|
|
33
|
+
deleteArticleViaAPI,
|
|
34
|
+
updateUserViaAPI,
|
|
35
|
+
getCurrentUserViaAPI,
|
|
36
|
+
type APIConfig,
|
|
37
|
+
type APIUser,
|
|
38
|
+
type APIAuthResponse,
|
|
39
|
+
} from './api';
|
|
40
|
+
|
|
41
|
+
// Setup utilities
|
|
42
|
+
export {
|
|
43
|
+
createUserInIsolation,
|
|
44
|
+
createUserViaAPI,
|
|
45
|
+
setupAuthenticatedPage,
|
|
46
|
+
createManyArticles,
|
|
47
|
+
cleanupTestData,
|
|
48
|
+
waitForAngular,
|
|
49
|
+
safeGoto,
|
|
50
|
+
type TestUser,
|
|
51
|
+
type IsolatedUserContext,
|
|
52
|
+
} from './setup';
|
|
53
|
+
|
|
54
|
+
// Configuration helpers
|
|
55
|
+
export {
|
|
56
|
+
getConfig,
|
|
57
|
+
isCI,
|
|
58
|
+
isDebug,
|
|
59
|
+
getBrowserLaunchOptions,
|
|
60
|
+
setTestEnv,
|
|
61
|
+
type SamaroConfig,
|
|
62
|
+
} from './config';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Browser, BrowserContext, Page, APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { APIUser } from './api';
|
|
3
|
+
export interface TestUser extends APIUser {
|
|
4
|
+
token?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IsolatedUserContext {
|
|
7
|
+
context: BrowserContext;
|
|
8
|
+
page: Page;
|
|
9
|
+
user: TestUser;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a user in an isolated browser context
|
|
13
|
+
* This provides full isolation for tests
|
|
14
|
+
*/
|
|
15
|
+
export declare function createUserInIsolation(browser: Browser, user?: Partial<TestUser>): Promise<IsolatedUserContext>;
|
|
16
|
+
/**
|
|
17
|
+
* Create a user via API (faster, no browser context needed)
|
|
18
|
+
*/
|
|
19
|
+
export declare function createUserViaAPI(request: APIRequestContext, user?: Partial<TestUser>): Promise<TestUser & {
|
|
20
|
+
token: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Setup authenticated page with token
|
|
24
|
+
*/
|
|
25
|
+
export declare function setupAuthenticatedPage(page: Page, token: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Create multiple articles via API
|
|
28
|
+
*/
|
|
29
|
+
export declare function createManyArticles(request: APIRequestContext, token: string, count: number, baseTitle?: string): Promise<Array<{
|
|
30
|
+
slug: string;
|
|
31
|
+
title: string;
|
|
32
|
+
}>>;
|
|
33
|
+
/**
|
|
34
|
+
* Cleanup helper for test data
|
|
35
|
+
*/
|
|
36
|
+
export declare function cleanupTestData(request: APIRequestContext, token: string, articles: Array<{
|
|
37
|
+
slug: string;
|
|
38
|
+
}>): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Wait for Angular to be stable
|
|
41
|
+
*/
|
|
42
|
+
export declare function waitForAngular(page: Page): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Safe navigation with retry
|
|
45
|
+
*/
|
|
46
|
+
export declare function safeGoto(page: Page, url: string, options?: {
|
|
47
|
+
timeout?: number;
|
|
48
|
+
retries?: number;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEpF,OAAO,EAAsB,OAAO,EAAE,MAAM,OAAO,CAAC;AAEpD,MAAM,WAAW,QAAS,SAAQ,OAAO;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GACvB,OAAO,CAAC,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CASvC;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAuB,GACjC,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAejD;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9D;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACnD,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
package/helpers/setup.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createUserInIsolation = createUserInIsolation;
|
|
37
|
+
exports.createUserViaAPI = createUserViaAPI;
|
|
38
|
+
exports.setupAuthenticatedPage = setupAuthenticatedPage;
|
|
39
|
+
exports.createManyArticles = createManyArticles;
|
|
40
|
+
exports.cleanupTestData = cleanupTestData;
|
|
41
|
+
exports.waitForAngular = waitForAngular;
|
|
42
|
+
exports.safeGoto = safeGoto;
|
|
43
|
+
const auth_1 = require("./auth");
|
|
44
|
+
const api_1 = require("./api");
|
|
45
|
+
/**
|
|
46
|
+
* Create a user in an isolated browser context
|
|
47
|
+
* This provides full isolation for tests
|
|
48
|
+
*/
|
|
49
|
+
async function createUserInIsolation(browser, user) {
|
|
50
|
+
const context = await browser.newContext();
|
|
51
|
+
const page = await context.newPage();
|
|
52
|
+
const uniqueUser = { ...(0, auth_1.generateUniqueUser)(), ...user };
|
|
53
|
+
const result = await (0, auth_1.register)(page, uniqueUser.username, uniqueUser.email, uniqueUser.password);
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
await context.close();
|
|
56
|
+
throw new Error(result.error || 'Failed to create user in isolation');
|
|
57
|
+
}
|
|
58
|
+
return { context, page, user: uniqueUser };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a user via API (faster, no browser context needed)
|
|
62
|
+
*/
|
|
63
|
+
async function createUserViaAPI(request, user) {
|
|
64
|
+
const uniqueUser = { ...(0, auth_1.generateUniqueUser)(), ...user };
|
|
65
|
+
const { token, username, email } = await (0, api_1.registerUserViaAPI)(request, uniqueUser);
|
|
66
|
+
return {
|
|
67
|
+
username,
|
|
68
|
+
email,
|
|
69
|
+
password: uniqueUser.password,
|
|
70
|
+
token
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Setup authenticated page with token
|
|
75
|
+
*/
|
|
76
|
+
async function setupAuthenticatedPage(page, token) {
|
|
77
|
+
// Set token in localStorage
|
|
78
|
+
await page.goto('/');
|
|
79
|
+
await page.evaluate((t) => {
|
|
80
|
+
localStorage.setItem('token', t);
|
|
81
|
+
}, token);
|
|
82
|
+
// Reload to apply token
|
|
83
|
+
await page.reload();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create multiple articles via API
|
|
87
|
+
*/
|
|
88
|
+
async function createManyArticles(request, token, count, baseTitle = 'Test Article') {
|
|
89
|
+
const { createArticleViaAPI } = await Promise.resolve().then(() => __importStar(require('./api')));
|
|
90
|
+
const articles = [];
|
|
91
|
+
for (let i = 0; i < count; i++) {
|
|
92
|
+
const article = await createArticleViaAPI(request, token, {
|
|
93
|
+
title: `${baseTitle} ${i + 1}`,
|
|
94
|
+
description: `Description for article ${i + 1}`,
|
|
95
|
+
body: `Body content for article ${i + 1}`,
|
|
96
|
+
tagList: ['test', `tag-${i}`],
|
|
97
|
+
});
|
|
98
|
+
articles.push(article);
|
|
99
|
+
}
|
|
100
|
+
return articles;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Cleanup helper for test data
|
|
104
|
+
*/
|
|
105
|
+
async function cleanupTestData(request, token, articles) {
|
|
106
|
+
const { deleteArticleViaAPI } = await Promise.resolve().then(() => __importStar(require('./api')));
|
|
107
|
+
await Promise.all(articles.map(article => deleteArticleViaAPI(request, token, article.slug).catch(() => {
|
|
108
|
+
// Ignore cleanup errors
|
|
109
|
+
})));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Wait for Angular to be stable
|
|
113
|
+
*/
|
|
114
|
+
async function waitForAngular(page) {
|
|
115
|
+
await page.waitForFunction(() => {
|
|
116
|
+
return window.getAngularTestability ?
|
|
117
|
+
window.getAngularTestability().isStable() : true;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Safe navigation with retry
|
|
122
|
+
*/
|
|
123
|
+
async function safeGoto(page, url, options = {}) {
|
|
124
|
+
const { timeout = 10000, retries = 3 } = options;
|
|
125
|
+
for (let i = 0; i < retries; i++) {
|
|
126
|
+
try {
|
|
127
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (i === retries - 1)
|
|
132
|
+
throw error;
|
|
133
|
+
await page.waitForTimeout(1000);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["setup.ts"],"names":[],"mappings":"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"}
|
package/helpers/setup.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Browser, BrowserContext, Page, APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { generateUniqueUser, register } from './auth';
|
|
3
|
+
import { registerUserViaAPI, APIUser } from './api';
|
|
4
|
+
|
|
5
|
+
export interface TestUser extends APIUser {
|
|
6
|
+
token?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IsolatedUserContext {
|
|
10
|
+
context: BrowserContext;
|
|
11
|
+
page: Page;
|
|
12
|
+
user: TestUser;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a user in an isolated browser context
|
|
17
|
+
* This provides full isolation for tests
|
|
18
|
+
*/
|
|
19
|
+
export async function createUserInIsolation(
|
|
20
|
+
browser: Browser,
|
|
21
|
+
user?: Partial<TestUser>
|
|
22
|
+
): Promise<IsolatedUserContext> {
|
|
23
|
+
const context = await browser.newContext();
|
|
24
|
+
const page = await context.newPage();
|
|
25
|
+
const uniqueUser = { ...generateUniqueUser(), ...user };
|
|
26
|
+
|
|
27
|
+
const result = await register(page, uniqueUser.username, uniqueUser.email, uniqueUser.password);
|
|
28
|
+
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
await context.close();
|
|
31
|
+
throw new Error(result.error || 'Failed to create user in isolation');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { context, page, user: uniqueUser };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a user via API (faster, no browser context needed)
|
|
39
|
+
*/
|
|
40
|
+
export async function createUserViaAPI(
|
|
41
|
+
request: APIRequestContext,
|
|
42
|
+
user?: Partial<TestUser>
|
|
43
|
+
): Promise<TestUser & { token: string }> {
|
|
44
|
+
const uniqueUser = { ...generateUniqueUser(), ...user };
|
|
45
|
+
const { token, username, email } = await registerUserViaAPI(request, uniqueUser);
|
|
46
|
+
return {
|
|
47
|
+
username,
|
|
48
|
+
email,
|
|
49
|
+
password: uniqueUser.password,
|
|
50
|
+
token
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Setup authenticated page with token
|
|
56
|
+
*/
|
|
57
|
+
export async function setupAuthenticatedPage(
|
|
58
|
+
page: Page,
|
|
59
|
+
token: string
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
// Set token in localStorage
|
|
62
|
+
await page.goto('/');
|
|
63
|
+
await page.evaluate((t) => {
|
|
64
|
+
localStorage.setItem('token', t);
|
|
65
|
+
}, token);
|
|
66
|
+
|
|
67
|
+
// Reload to apply token
|
|
68
|
+
await page.reload();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create multiple articles via API
|
|
73
|
+
*/
|
|
74
|
+
export async function createManyArticles(
|
|
75
|
+
request: APIRequestContext,
|
|
76
|
+
token: string,
|
|
77
|
+
count: number,
|
|
78
|
+
baseTitle: string = 'Test Article'
|
|
79
|
+
): Promise<Array<{ slug: string; title: string }>> {
|
|
80
|
+
const { createArticleViaAPI } = await import('./api');
|
|
81
|
+
const articles: Array<{ slug: string; title: string }> = [];
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < count; i++) {
|
|
84
|
+
const article = await createArticleViaAPI(request, token, {
|
|
85
|
+
title: `${baseTitle} ${i + 1}`,
|
|
86
|
+
description: `Description for article ${i + 1}`,
|
|
87
|
+
body: `Body content for article ${i + 1}`,
|
|
88
|
+
tagList: ['test', `tag-${i}`],
|
|
89
|
+
});
|
|
90
|
+
articles.push(article);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return articles;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Cleanup helper for test data
|
|
98
|
+
*/
|
|
99
|
+
export async function cleanupTestData(
|
|
100
|
+
request: APIRequestContext,
|
|
101
|
+
token: string,
|
|
102
|
+
articles: Array<{ slug: string }>
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const { deleteArticleViaAPI } = await import('./api');
|
|
105
|
+
|
|
106
|
+
await Promise.all(
|
|
107
|
+
articles.map(article =>
|
|
108
|
+
deleteArticleViaAPI(request, token, article.slug).catch(() => {
|
|
109
|
+
// Ignore cleanup errors
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Wait for Angular to be stable
|
|
117
|
+
*/
|
|
118
|
+
export async function waitForAngular(page: Page): Promise<void> {
|
|
119
|
+
await page.waitForFunction(() => {
|
|
120
|
+
return (window as any).getAngularTestability ?
|
|
121
|
+
(window as any).getAngularTestability().isStable() : true;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Safe navigation with retry
|
|
127
|
+
*/
|
|
128
|
+
export async function safeGoto(
|
|
129
|
+
page: Page,
|
|
130
|
+
url: string,
|
|
131
|
+
options: { timeout?: number; retries?: number } = {}
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const { timeout = 10000, retries = 3 } = options;
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < retries; i++) {
|
|
136
|
+
try {
|
|
137
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout });
|
|
138
|
+
return;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (i === retries - 1) throw error;
|
|
141
|
+
await page.waitForTimeout(1000);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "samaro-uitest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Comprehensive testing framework for Angular with Vitest, Playwright, and Allure reporting",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"samaro": "./bin/samaro-init.js",
|
|
9
|
+
"samaro-init": "./bin/samaro-init.js",
|
|
10
|
+
"samaro-test": "./bin/samaro-test.js",
|
|
11
|
+
"samaro-allure": "./bin/samaro-allure.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/**/*",
|
|
15
|
+
"bin/**/*",
|
|
16
|
+
"config/**/*",
|
|
17
|
+
"helpers/**/*",
|
|
18
|
+
"scripts/**/*",
|
|
19
|
+
"schematics/**/*",
|
|
20
|
+
"types/**/*",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prebuild": "rimraf dist",
|
|
27
|
+
"postbuild": "cpy 'config/**/*' 'helpers/**/*' 'scripts/**/*' 'types/**/*' dist/ --parents",
|
|
28
|
+
"prepublishOnly": "npm run build",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"format": "prettier --write ."
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"angular",
|
|
34
|
+
"testing",
|
|
35
|
+
"vitest",
|
|
36
|
+
"playwright",
|
|
37
|
+
"allure",
|
|
38
|
+
"e2e",
|
|
39
|
+
"component-testing",
|
|
40
|
+
"test-framework",
|
|
41
|
+
"automation"
|
|
42
|
+
],
|
|
43
|
+
"author": "Samaro",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/samaro/samaro-uitest.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/samaro/samaro-uitest/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/samaro/samaro-uitest#readme",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0",
|
|
55
|
+
"npm": ">=8.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@angular/core": ">=15.0.0",
|
|
59
|
+
"@angular/platform-browser-dynamic": ">=15.0.0",
|
|
60
|
+
"typescript": ">=4.8.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@analogjs/vite-plugin-angular": "^2.2.2",
|
|
64
|
+
"@playwright/test": "^1.58.0",
|
|
65
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
66
|
+
"@vitest/ui": "^4.0.18",
|
|
67
|
+
"allure": "^3.14.2",
|
|
68
|
+
"allure-commandline": "^2.43.0",
|
|
69
|
+
"allure-js-commons": "^3.10.2",
|
|
70
|
+
"allure-playwright": "^3.10.2",
|
|
71
|
+
"allure-vitest": "^3.10.2",
|
|
72
|
+
"jsdom": "^27.4.0",
|
|
73
|
+
"playwright": "^1.58.0",
|
|
74
|
+
"vitest": "^4.0.18",
|
|
75
|
+
"zone.js": "^0.16.0",
|
|
76
|
+
"cross-spawn": "^7.0.3",
|
|
77
|
+
"chalk": "^4.1.2",
|
|
78
|
+
"inquirer": "^8.2.6"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@angular-devkit/schematics": "^17.0.0",
|
|
82
|
+
"@types/cross-spawn": "^6.0.6",
|
|
83
|
+
"@types/inquirer": "^9.0.7",
|
|
84
|
+
"@types/node": "^20.0.0",
|
|
85
|
+
"cpy-cli": "^5.0.0",
|
|
86
|
+
"prettier": "^3.0.0",
|
|
87
|
+
"rimraf": "^5.0.5",
|
|
88
|
+
"typescript": "^5.3.0"
|
|
89
|
+
},
|
|
90
|
+
"schematics": "./schematics/collection.json",
|
|
91
|
+
"ng-add": {
|
|
92
|
+
"save": "devDependencies"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "@angular-devkit/schematics/collection-schema.json",
|
|
3
|
+
"schematics": {
|
|
4
|
+
"ng-add": {
|
|
5
|
+
"description": "Add SamaroUItest to the project",
|
|
6
|
+
"factory": "./ng-add/index#ngAdd",
|
|
7
|
+
"schema": "./ng-add/schema.json",
|
|
8
|
+
"aliases": ["install", "setup"]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const { chain, externalSchematic, schematic } = require('@angular-devkit/schematics');
|
|
2
|
+
const { NodePackageInstallTask } = require('@angular-devkit/schematics/tasks');
|
|
3
|
+
|
|
4
|
+
function ngAdd(options) {
|
|
5
|
+
return (tree, context) => {
|
|
6
|
+
context.addTask(new NodePackageInstallTask());
|
|
7
|
+
|
|
8
|
+
context.logger.info('🚀 Installing SamaroUItest...');
|
|
9
|
+
|
|
10
|
+
return chain([
|
|
11
|
+
updatePackageJson(),
|
|
12
|
+
createConfigFiles(options),
|
|
13
|
+
createDirectories(),
|
|
14
|
+
])(tree, context);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function updatePackageJson() {
|
|
19
|
+
return (tree, context) => {
|
|
20
|
+
const packageJsonPath = '/package.json';
|
|
21
|
+
const packageJsonBuffer = tree.read(packageJsonPath);
|
|
22
|
+
|
|
23
|
+
if (!packageJsonBuffer) {
|
|
24
|
+
throw new Error('Could not read package.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const packageJson = JSON.parse(packageJsonBuffer.toString());
|
|
28
|
+
|
|
29
|
+
packageJson.scripts = {
|
|
30
|
+
...packageJson.scripts,
|
|
31
|
+
'test': 'samaro-test vitest',
|
|
32
|
+
'test:run': 'samaro-test "vitest --run"',
|
|
33
|
+
'test:ui': 'samaro-test "vitest --ui"',
|
|
34
|
+
'test:coverage': 'samaro-test "vitest --coverage"',
|
|
35
|
+
'test:e2e': 'samaro-test "playwright test"',
|
|
36
|
+
'test:e2e:ui': 'samaro-test "playwright test --ui"',
|
|
37
|
+
'test:e2e:headed': 'samaro-test "playwright test --headed"',
|
|
38
|
+
'test:e2e:debug': 'samaro-test "playwright test --debug"',
|
|
39
|
+
'test:ct': 'samaro-test "playwright test --config playwright.component.config.ts"',
|
|
40
|
+
'test:ct:ui': 'samaro-test "playwright test --config playwright.component.config.ts --ui"',
|
|
41
|
+
'allure:generate': 'samaro-allure generate',
|
|
42
|
+
'allure:open': 'samaro-allure open',
|
|
43
|
+
'allure:clean': 'samaro-allure clean',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
tree.overwrite(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
47
|
+
context.logger.info('✅ Updated package.json scripts');
|
|
48
|
+
|
|
49
|
+
return tree;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createConfigFiles(options) {
|
|
54
|
+
return (tree, context) => {
|
|
55
|
+
// Create vitest.config.ts
|
|
56
|
+
tree.create('/vitest.config.ts', `import { createVitestConfig } from 'samaro-uitest/config';
|
|
57
|
+
|
|
58
|
+
export default createVitestConfig({
|
|
59
|
+
projectRoot: __dirname,
|
|
60
|
+
testGlob: 'src/**/*.spec.ts',
|
|
61
|
+
resultsDir: 'allure-unit-results',
|
|
62
|
+
});
|
|
63
|
+
`);
|
|
64
|
+
context.logger.info('✅ Created vitest.config.ts');
|
|
65
|
+
|
|
66
|
+
// Create playwright.config.ts
|
|
67
|
+
tree.create('/playwright.config.ts', `import { createPlaywrightConfig } from 'samaro-uitest/config';
|
|
68
|
+
|
|
69
|
+
export default createPlaywrightConfig({
|
|
70
|
+
baseURL: 'http://localhost:4200',
|
|
71
|
+
testDir: './e2e',
|
|
72
|
+
resultsDir: 'allure-e2e-results',
|
|
73
|
+
webServerCommand: 'npm run start',
|
|
74
|
+
webServerPort: 4200,
|
|
75
|
+
});
|
|
76
|
+
`);
|
|
77
|
+
context.logger.info('✅ Created playwright.config.ts');
|
|
78
|
+
|
|
79
|
+
// Create playwright.component.config.ts
|
|
80
|
+
tree.create('/playwright.component.config.ts', `import { createComponentConfig } from 'samaro-uitest/config';
|
|
81
|
+
|
|
82
|
+
export default createComponentConfig({
|
|
83
|
+
baseURL: 'http://localhost:4200',
|
|
84
|
+
testDir: './ct-tests',
|
|
85
|
+
resultsDir: 'allure-ct-results',
|
|
86
|
+
});
|
|
87
|
+
`);
|
|
88
|
+
context.logger.info('✅ Created playwright.component.config.ts');
|
|
89
|
+
|
|
90
|
+
// Create test-setup.ts in src if it exists
|
|
91
|
+
if (tree.exists('/src')) {
|
|
92
|
+
tree.create('/src/test-setup.ts', `import 'zone.js';
|
|
93
|
+
import 'zone.js/testing';
|
|
94
|
+
import { getTestBed } from '@angular/core/testing';
|
|
95
|
+
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
|
96
|
+
|
|
97
|
+
getTestBed().initTestEnvironment(
|
|
98
|
+
BrowserDynamicTestingModule,
|
|
99
|
+
platformBrowserDynamicTesting()
|
|
100
|
+
);
|
|
101
|
+
`);
|
|
102
|
+
context.logger.info('✅ Created src/test-setup.ts');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return tree;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function createDirectories() {
|
|
110
|
+
return (tree, context) => {
|
|
111
|
+
tree.create('/e2e/.gitkeep', '');
|
|
112
|
+
tree.create('/e2e/helpers/.gitkeep', '');
|
|
113
|
+
tree.create('/ct-tests/.gitkeep', '');
|
|
114
|
+
|
|
115
|
+
context.logger.info('✅ Created test directories');
|
|
116
|
+
|
|
117
|
+
return tree;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { ngAdd };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SamaroUItestNgAddSchema",
|
|
4
|
+
"title": "SamaroUItest ng-add Options Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"project": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The name of the project to add SamaroUItest to",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "projectName"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"skipInstall": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"default": false,
|
|
17
|
+
"description": "Skip installing dependencies"
|
|
18
|
+
},
|
|
19
|
+
"includeSampleTests": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"default": true,
|
|
22
|
+
"description": "Include sample test files"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|