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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +336 -0
  3. package/bin/samaro-allure.js +172 -0
  4. package/bin/samaro-init.js +278 -0
  5. package/bin/samaro-test.js +36 -0
  6. package/config/index.d.ts +4 -0
  7. package/config/index.d.ts.map +1 -0
  8. package/config/index.js +11 -0
  9. package/config/index.js.map +1 -0
  10. package/config/index.ts +3 -0
  11. package/config/playwright.component.config.d.ts +12 -0
  12. package/config/playwright.component.config.d.ts.map +1 -0
  13. package/config/playwright.component.config.js +42 -0
  14. package/config/playwright.component.config.js.map +1 -0
  15. package/config/playwright.component.config.ts +60 -0
  16. package/config/playwright.config.d.ts +14 -0
  17. package/config/playwright.config.d.ts.map +1 -0
  18. package/config/playwright.config.js +53 -0
  19. package/config/playwright.config.js.map +1 -0
  20. package/config/playwright.config.ts +76 -0
  21. package/config/test-setup.ts +17 -0
  22. package/config/vitest.config.d.ts +13 -0
  23. package/config/vitest.config.d.ts.map +1 -0
  24. package/config/vitest.config.js +53 -0
  25. package/config/vitest.config.js.map +1 -0
  26. package/config/vitest.config.ts +64 -0
  27. package/dist/config/index.d.ts +4 -0
  28. package/dist/config/index.d.ts.map +1 -0
  29. package/dist/config/index.js +4 -0
  30. package/dist/config/index.js.map +1 -0
  31. package/dist/config/playwright.component.config.d.ts +12 -0
  32. package/dist/config/playwright.component.config.d.ts.map +1 -0
  33. package/dist/config/playwright.component.config.js +39 -0
  34. package/dist/config/playwright.component.config.js.map +1 -0
  35. package/dist/config/playwright.config.d.ts +14 -0
  36. package/dist/config/playwright.config.d.ts.map +1 -0
  37. package/dist/config/playwright.config.js +50 -0
  38. package/dist/config/playwright.config.js.map +1 -0
  39. package/dist/config/test-setup.d.ts +3 -0
  40. package/dist/config/test-setup.d.ts.map +1 -0
  41. package/dist/config/test-setup.js +17 -0
  42. package/dist/config/test-setup.js.map +1 -0
  43. package/dist/config/vitest.config.d.ts +13 -0
  44. package/dist/config/vitest.config.d.ts.map +1 -0
  45. package/dist/config/vitest.config.js +46 -0
  46. package/dist/config/vitest.config.js.map +1 -0
  47. package/dist/helpers/api.d.ts +69 -0
  48. package/dist/helpers/api.d.ts.map +1 -0
  49. package/dist/helpers/api.js +154 -0
  50. package/dist/helpers/api.js.map +1 -0
  51. package/dist/helpers/auth.d.ts +35 -0
  52. package/dist/helpers/auth.d.ts.map +1 -0
  53. package/dist/helpers/auth.js +96 -0
  54. package/dist/helpers/auth.js.map +1 -0
  55. package/dist/helpers/config.d.ts +34 -0
  56. package/dist/helpers/config.d.ts.map +1 -0
  57. package/dist/helpers/config.js +63 -0
  58. package/dist/helpers/config.js.map +1 -0
  59. package/dist/helpers/debug.d.ts +48 -0
  60. package/dist/helpers/debug.d.ts.map +1 -0
  61. package/dist/helpers/debug.js +76 -0
  62. package/dist/helpers/debug.js.map +1 -0
  63. package/dist/helpers/index.d.ts +6 -0
  64. package/dist/helpers/index.d.ts.map +1 -0
  65. package/dist/helpers/index.js +11 -0
  66. package/dist/helpers/index.js.map +1 -0
  67. package/dist/helpers/setup.d.ts +50 -0
  68. package/dist/helpers/setup.d.ts.map +1 -0
  69. package/dist/helpers/setup.js +95 -0
  70. package/dist/helpers/setup.js.map +1 -0
  71. package/dist/src/index.d.ts +16 -0
  72. package/dist/src/index.d.ts.map +1 -0
  73. package/dist/src/index.js +38 -0
  74. package/dist/src/index.js.map +1 -0
  75. package/helpers/api.d.ts +69 -0
  76. package/helpers/api.d.ts.map +1 -0
  77. package/helpers/api.js +163 -0
  78. package/helpers/api.js.map +1 -0
  79. package/helpers/api.ts +224 -0
  80. package/helpers/auth.d.ts +35 -0
  81. package/helpers/auth.d.ts.map +1 -0
  82. package/helpers/auth.js +104 -0
  83. package/helpers/auth.js.map +1 -0
  84. package/helpers/auth.ts +125 -0
  85. package/helpers/config.d.ts +34 -0
  86. package/helpers/config.d.ts.map +1 -0
  87. package/helpers/config.js +70 -0
  88. package/helpers/config.js.map +1 -0
  89. package/helpers/config.ts +80 -0
  90. package/helpers/debug.d.ts +48 -0
  91. package/helpers/debug.d.ts.map +1 -0
  92. package/helpers/debug.js +86 -0
  93. package/helpers/debug.js.map +1 -0
  94. package/helpers/debug.ts +111 -0
  95. package/helpers/index.d.ts +6 -0
  96. package/helpers/index.d.ts.map +1 -0
  97. package/helpers/index.js +47 -0
  98. package/helpers/index.js.map +1 -0
  99. package/helpers/index.ts +62 -0
  100. package/helpers/setup.d.ts +50 -0
  101. package/helpers/setup.d.ts.map +1 -0
  102. package/helpers/setup.js +137 -0
  103. package/helpers/setup.js.map +1 -0
  104. package/helpers/setup.ts +144 -0
  105. package/package.json +94 -0
  106. package/schematics/collection.json +11 -0
  107. package/schematics/ng-add/index.js +121 -0
  108. package/schematics/ng-add/schema.json +26 -0
  109. package/scripts/check-allure-results.js +269 -0
  110. package/types/allure.d.ts +76 -0
  111. package/types/global.d.ts +49 -0
@@ -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"}
@@ -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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"}
@@ -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
+ }