@immense/vue-pom-generator 1.0.47 → 1.0.48
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 +30 -11
- package/RELEASE_NOTES.md +38 -34
- package/class-generation/index.ts +1421 -682
- package/dist/class-generation/index.d.ts +8 -0
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/index.cjs +1437 -689
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1440 -692
- package/dist/index.mjs.map +1 -1
- package/dist/manifest-generator.d.ts.map +1 -1
- package/dist/method-generation.d.ts +2 -0
- package/dist/method-generation.d.ts.map +1 -1
- package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts +2 -1
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts +2 -1
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support-plugins.d.ts +2 -1
- package/dist/plugin/support-plugins.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +15 -7
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/tests/fixtures/generated-tsc/BasePage.full.d.ts +19 -0
- package/dist/tests/fixtures/generated-tsc/BasePage.full.d.ts.map +1 -0
- package/dist/tests/fixtures/generated-tsc/BasePage.minimal.d.ts +8 -0
- package/dist/tests/fixtures/generated-tsc/BasePage.minimal.d.ts.map +1 -0
- package/dist/tests/fixtures/generated-tsc/Pointer.d.ts +6 -0
- package/dist/tests/fixtures/generated-tsc/Pointer.d.ts.map +1 -0
- package/dist/typescript-codegen.d.ts +34 -0
- package/dist/typescript-codegen.d.ts.map +1 -0
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -3,11 +3,12 @@ import process from "node:process";
|
|
|
3
3
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import * as compilerDom from "@vue/compiler-dom";
|
|
6
|
-
import { parse as parse$2 } from "@vue/compiler-dom";
|
|
6
|
+
import { parse as parse$2, NodeTypes as NodeTypes$1 } from "@vue/compiler-dom";
|
|
7
7
|
import { parse as parse$1, compileScript } from "@vue/compiler-sfc";
|
|
8
8
|
import { parseExpression, parse } from "@babel/parser";
|
|
9
|
+
import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression, ElementTypes } from "@vue/compiler-core";
|
|
10
|
+
import { Project, QuoteKind, NewLineKind, IndentationText, StructureKind, CodeBlockWriter, VariableDeclarationKind } from "ts-morph";
|
|
9
11
|
import { JSDOM } from "jsdom";
|
|
10
|
-
import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression } from "@vue/compiler-core";
|
|
11
12
|
import { isArrayExpression, isStringLiteral, isTemplateLiteral, isAssignmentExpression, isIdentifier, isMemberExpression, isCallExpression, isExpressionStatement, isArrowFunctionExpression, isOptionalMemberExpression, isObjectExpression, isFile, isBlockStatement, isOptionalCallExpression, isLogicalExpression, isConditionalExpression, isSequenceExpression, isAssignmentPattern, isRestElement, isObjectPattern, isObjectProperty, isProgram, VISITOR_KEYS, isBooleanLiteral, isNumericLiteral, isNullLiteral } from "@babel/types";
|
|
12
13
|
import { performance } from "node:perf_hooks";
|
|
13
14
|
import virtualImport from "vite-plugin-virtual";
|
|
@@ -62,9 +63,97 @@ function createLogger(options) {
|
|
|
62
63
|
}
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
function createTypeScriptProject() {
|
|
67
|
+
return new Project({
|
|
68
|
+
useInMemoryFileSystem: true,
|
|
69
|
+
manipulationSettings: {
|
|
70
|
+
indentationText: IndentationText.FourSpaces,
|
|
71
|
+
newLineKind: NewLineKind.LineFeed,
|
|
72
|
+
quoteKind: QuoteKind.Double,
|
|
73
|
+
useTrailingCommas: false
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function ensureTrailingNewline(text) {
|
|
78
|
+
return text.endsWith("\n") ? text : `${text}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
function createTypeScriptWriter() {
|
|
82
|
+
return new CodeBlockWriter({
|
|
83
|
+
newLine: "\n",
|
|
84
|
+
useTabs: false,
|
|
85
|
+
indentNumberOfSpaces: 4
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function renderTypeScript(write) {
|
|
89
|
+
const writer = createTypeScriptWriter();
|
|
90
|
+
write(writer);
|
|
91
|
+
return ensureTrailingNewline(writer.toString());
|
|
92
|
+
}
|
|
93
|
+
function buildCommentBlock(lines) {
|
|
94
|
+
return renderTypeScript((writer) => {
|
|
95
|
+
writer.writeLine("/**");
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
writer.writeLine(` * ${line}`);
|
|
98
|
+
}
|
|
99
|
+
writer.writeLine(" */");
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function buildFilePrefix(options = {}) {
|
|
103
|
+
let prefix = "";
|
|
104
|
+
if (options.referenceLib) {
|
|
105
|
+
prefix += `/// <reference lib="${options.referenceLib}" />
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
if (options.eslintDisableSortImports) {
|
|
109
|
+
prefix += "/* eslint-disable perfectionist/sort-imports */\n";
|
|
110
|
+
}
|
|
111
|
+
if (options.commentLines?.length) {
|
|
112
|
+
prefix += buildCommentBlock(options.commentLines);
|
|
113
|
+
}
|
|
114
|
+
return prefix;
|
|
115
|
+
}
|
|
116
|
+
function renderSourceFile(filePath, build, options = {}) {
|
|
117
|
+
const project = createTypeScriptProject();
|
|
118
|
+
const sourceFile = project.createSourceFile(filePath, "", { overwrite: true });
|
|
119
|
+
build(sourceFile);
|
|
120
|
+
const content = ensureTrailingNewline(sourceFile.getFullText());
|
|
121
|
+
return options.prefixText ? ensureTrailingNewline(`${options.prefixText}${content}`) : content;
|
|
122
|
+
}
|
|
123
|
+
function addNamedImport(sourceFile, options) {
|
|
124
|
+
return sourceFile.addImportDeclaration({
|
|
125
|
+
moduleSpecifier: options.moduleSpecifier,
|
|
126
|
+
isTypeOnly: options.isTypeOnly,
|
|
127
|
+
namedImports: options.namedImports
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function addExportAll(sourceFile, moduleSpecifier) {
|
|
131
|
+
return sourceFile.addExportDeclaration({ moduleSpecifier });
|
|
132
|
+
}
|
|
133
|
+
function createClassMethod(method) {
|
|
134
|
+
return {
|
|
135
|
+
kind: StructureKind.Method,
|
|
136
|
+
...method
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function createClassProperty(property) {
|
|
140
|
+
return {
|
|
141
|
+
kind: StructureKind.Property,
|
|
142
|
+
...property
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function createClassGetter(getter) {
|
|
146
|
+
return {
|
|
147
|
+
kind: StructureKind.GetAccessor,
|
|
148
|
+
...getter
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function createClassConstructor(constructorDeclaration) {
|
|
152
|
+
return {
|
|
153
|
+
kind: StructureKind.Constructor,
|
|
154
|
+
...constructorDeclaration
|
|
155
|
+
};
|
|
156
|
+
}
|
|
68
157
|
function upperFirst$1(value) {
|
|
69
158
|
if (!value) {
|
|
70
159
|
return value;
|
|
@@ -74,12 +163,34 @@ function upperFirst$1(value) {
|
|
|
74
163
|
function hasParam(params, name) {
|
|
75
164
|
return Object.prototype.hasOwnProperty.call(params, name);
|
|
76
165
|
}
|
|
77
|
-
function
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
166
|
+
function splitTypeAndInitializer(typeExpression) {
|
|
167
|
+
const trimmed = typeExpression.trim();
|
|
168
|
+
const initializerIndex = trimmed.lastIndexOf("=");
|
|
169
|
+
if (initializerIndex < 0) {
|
|
170
|
+
return { type: trimmed };
|
|
81
171
|
}
|
|
82
|
-
return
|
|
172
|
+
return {
|
|
173
|
+
type: trimmed.slice(0, initializerIndex).trim(),
|
|
174
|
+
initializer: trimmed.slice(initializerIndex + 1).trim()
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function createParameter(name, typeExpression) {
|
|
178
|
+
const { type, initializer } = splitTypeAndInitializer(typeExpression);
|
|
179
|
+
return {
|
|
180
|
+
name,
|
|
181
|
+
type: type || void 0,
|
|
182
|
+
initializer
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function createParameters(params) {
|
|
186
|
+
return Object.entries(params).map(([name, typeExpression]) => createParameter(name, typeExpression));
|
|
187
|
+
}
|
|
188
|
+
function createInlineParameter(name, options = {}) {
|
|
189
|
+
return {
|
|
190
|
+
name,
|
|
191
|
+
type: options.type,
|
|
192
|
+
initializer: options.initializer
|
|
193
|
+
};
|
|
83
194
|
}
|
|
84
195
|
function removeByKeySegment(value) {
|
|
85
196
|
const idx = value.lastIndexOf("ByKey");
|
|
@@ -107,113 +218,135 @@ function uniqueAlternates(primary, alternates) {
|
|
|
107
218
|
function testIdExpression(formattedDataTestId) {
|
|
108
219
|
return formattedDataTestId.includes("${") ? `\`${formattedDataTestId}\`` : JSON.stringify(formattedDataTestId);
|
|
109
220
|
}
|
|
221
|
+
function createAsyncMethod(name, parameters, statements) {
|
|
222
|
+
return createClassMethod({
|
|
223
|
+
name,
|
|
224
|
+
isAsync: true,
|
|
225
|
+
parameters,
|
|
226
|
+
statements
|
|
227
|
+
});
|
|
228
|
+
}
|
|
110
229
|
function generateClickMethod(methodName, formattedDataTestId, alternateFormattedDataTestIds, params) {
|
|
111
|
-
let content;
|
|
112
230
|
const name = `click${methodName}`;
|
|
113
231
|
const noWaitName = `${name}NoWait`;
|
|
114
|
-
const
|
|
115
|
-
const paramBlockWithWait = paramBlock ? `${paramBlock}, wait: boolean = true` : "wait: boolean = true";
|
|
232
|
+
const baseParameters = createParameters(params);
|
|
116
233
|
const argsForForward = Object.keys(params).join(", ");
|
|
117
234
|
const alternates = uniqueAlternates(formattedDataTestId, alternateFormattedDataTestIds);
|
|
118
235
|
if (alternates.length > 0) {
|
|
119
236
|
const candidatesExpr = [formattedDataTestId, ...alternates].map(testIdExpression).join(", ");
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
${
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
237
|
+
const clickMethod = createAsyncMethod(
|
|
238
|
+
name,
|
|
239
|
+
hasParam(params, "key") ? [...baseParameters, createInlineParameter("wait", { type: "boolean", initializer: "true" })] : [createInlineParameter("wait", { type: "boolean", initializer: "true" })],
|
|
240
|
+
(writer) => {
|
|
241
|
+
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
242
|
+
writer.writeLine("let lastError: unknown;");
|
|
243
|
+
writer.write("for (const testId of candidates) ").block(() => {
|
|
244
|
+
writer.writeLine("const locator = this.locatorByTestId(testId);");
|
|
245
|
+
writer.write("try ").block(() => {
|
|
246
|
+
writer.write("if (await locator.count()) ").block(() => {
|
|
247
|
+
writer.writeLine('await this.clickLocator(locator, "", wait);');
|
|
248
|
+
writer.writeLine("return;");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
writer.write("catch (e) ").block(() => {
|
|
252
|
+
writer.writeLine("lastError = e;");
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
writer.writeLine(`throw (lastError instanceof Error) ? lastError : new Error("[pom] Failed to click any candidate locator for ${name}.");`);
|
|
256
|
+
}
|
|
257
|
+
);
|
|
140
258
|
const noWaitArgs = argsForForward ? `${argsForForward}, false` : "false";
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
259
|
+
const noWaitMethod = createAsyncMethod(
|
|
260
|
+
noWaitName,
|
|
261
|
+
hasParam(params, "key") ? baseParameters : [],
|
|
262
|
+
(writer) => {
|
|
263
|
+
writer.writeLine(`await this.${name}(${noWaitArgs});`);
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
return [clickMethod, noWaitMethod];
|
|
147
267
|
}
|
|
148
268
|
if (hasParam(params, "key")) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`;
|
|
158
|
-
} else {
|
|
159
|
-
content = `${INDENT}async ${name}(wait: boolean = true) {
|
|
160
|
-
${INDENT2}await this.clickByTestId("${formattedDataTestId}", "", wait);
|
|
161
|
-
${INDENT}}
|
|
162
|
-
`;
|
|
163
|
-
content += `
|
|
164
|
-
${INDENT}async ${noWaitName}() {
|
|
165
|
-
${INDENT2}await this.${name}(false);
|
|
166
|
-
${INDENT}}
|
|
167
|
-
`;
|
|
269
|
+
return [
|
|
270
|
+
createAsyncMethod(name, [...baseParameters, createInlineParameter("wait", { type: "boolean", initializer: "true" })], (writer) => {
|
|
271
|
+
writer.writeLine(`await this.clickByTestId(\`${formattedDataTestId}\`, "", wait);`);
|
|
272
|
+
}),
|
|
273
|
+
createAsyncMethod(noWaitName, baseParameters, (writer) => {
|
|
274
|
+
writer.writeLine(`await this.${name}(${argsForForward}, false);`);
|
|
275
|
+
})
|
|
276
|
+
];
|
|
168
277
|
}
|
|
169
|
-
return
|
|
278
|
+
return [
|
|
279
|
+
createAsyncMethod(name, [createInlineParameter("wait", { type: "boolean", initializer: "true" })], (writer) => {
|
|
280
|
+
writer.writeLine(`await this.clickByTestId("${formattedDataTestId}", "", wait);`);
|
|
281
|
+
}),
|
|
282
|
+
createAsyncMethod(noWaitName, [], (writer) => {
|
|
283
|
+
writer.writeLine(`await this.${name}(false);`);
|
|
284
|
+
})
|
|
285
|
+
];
|
|
170
286
|
}
|
|
171
287
|
function generateRadioMethod(methodName, formattedDataTestId) {
|
|
172
288
|
const name = `select${methodName}`;
|
|
173
289
|
const hasKey = formattedDataTestId.includes("${key}");
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
290
|
+
const parameters = hasKey ? [
|
|
291
|
+
createInlineParameter("key", { type: "string" }),
|
|
292
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
293
|
+
] : [createInlineParameter("annotationText", { type: "string", initializer: '""' })];
|
|
294
|
+
const testIdExpr = hasKey ? `\`${formattedDataTestId}\`` : `"${formattedDataTestId}"`;
|
|
295
|
+
return [
|
|
296
|
+
createAsyncMethod(name, parameters, (writer) => {
|
|
297
|
+
writer.writeLine(`await this.clickByTestId(${testIdExpr}, annotationText);`);
|
|
298
|
+
})
|
|
299
|
+
];
|
|
184
300
|
}
|
|
185
301
|
function generateSelectMethod(methodName, formattedDataTestId) {
|
|
186
302
|
const name = `select${methodName}`;
|
|
187
303
|
const needsKey = formattedDataTestId.includes("${key}");
|
|
188
304
|
const selectorExpr = needsKey ? `this.selectorForTestId(\`${formattedDataTestId}\`)` : `this.selectorForTestId("${formattedDataTestId}")`;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
305
|
+
return [
|
|
306
|
+
createAsyncMethod(
|
|
307
|
+
name,
|
|
308
|
+
[
|
|
309
|
+
createInlineParameter("value", { type: "string" }),
|
|
310
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
311
|
+
],
|
|
312
|
+
(writer) => {
|
|
313
|
+
writer.writeLine(`const selector = ${selectorExpr};`);
|
|
314
|
+
writer.writeLine("await this.animateCursorToElement(selector, false, 500, annotationText);");
|
|
315
|
+
writer.writeLine("await this.page.selectOption(selector, value);");
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
];
|
|
197
319
|
}
|
|
198
320
|
function generateVSelectMethod(methodName, formattedDataTestId) {
|
|
199
321
|
const name = `select${methodName}`;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
322
|
+
return [
|
|
323
|
+
createAsyncMethod(
|
|
324
|
+
name,
|
|
325
|
+
[
|
|
326
|
+
createInlineParameter("value", { type: "string" }),
|
|
327
|
+
createInlineParameter("timeOut", { type: "number", initializer: "500" }),
|
|
328
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
329
|
+
],
|
|
330
|
+
(writer) => {
|
|
331
|
+
writer.writeLine(`await this.selectVSelectByTestId("${formattedDataTestId}", value, timeOut, annotationText);`);
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
];
|
|
209
335
|
}
|
|
210
336
|
function generateTypeMethod(methodName, formattedDataTestId) {
|
|
211
337
|
const name = `type${methodName}`;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
338
|
+
return [
|
|
339
|
+
createAsyncMethod(
|
|
340
|
+
name,
|
|
341
|
+
[
|
|
342
|
+
createInlineParameter("text", { type: "string" }),
|
|
343
|
+
createInlineParameter("annotationText", { type: "string", initializer: '""' })
|
|
344
|
+
],
|
|
345
|
+
(writer) => {
|
|
346
|
+
writer.writeLine(`await this.fillInputByTestId("${formattedDataTestId}", text, annotationText);`);
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
];
|
|
217
350
|
}
|
|
218
351
|
function isAllDigits(value) {
|
|
219
352
|
if (!value)
|
|
@@ -235,90 +368,119 @@ function generateGetElementByDataTestId(methodName, nativeRole, formattedDataTes
|
|
|
235
368
|
if (needsKey) {
|
|
236
369
|
const keyType = params.key || "string";
|
|
237
370
|
const keyedPropertyName = getterNameOverride ?? removeByKeySegment(propertyName);
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
371
|
+
return [
|
|
372
|
+
createClassGetter({
|
|
373
|
+
name: keyedPropertyName,
|
|
374
|
+
statements: [
|
|
375
|
+
`return this.keyedLocators((key: ${keyType}) => this.locatorByTestId(\`${formattedDataTestId}\`));`
|
|
376
|
+
]
|
|
377
|
+
})
|
|
378
|
+
];
|
|
243
379
|
}
|
|
244
380
|
const finalPropertyName = getterNameOverride ?? propertyName;
|
|
245
381
|
const alternates = uniqueAlternates(formattedDataTestId, alternateFormattedDataTestIds);
|
|
246
382
|
if (alternates.length > 0) {
|
|
247
383
|
const all = [formattedDataTestId, ...alternates];
|
|
248
384
|
const locatorExpr = all.map((id) => `this.locatorByTestId(${testIdExpression(id)})`).reduce((acc, next) => `${acc}.or(${next})`);
|
|
249
|
-
return
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
385
|
+
return [
|
|
386
|
+
createClassGetter({
|
|
387
|
+
name: finalPropertyName,
|
|
388
|
+
statements: [`return ${locatorExpr};`]
|
|
389
|
+
})
|
|
390
|
+
];
|
|
254
391
|
}
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
392
|
+
return [
|
|
393
|
+
createClassGetter({
|
|
394
|
+
name: finalPropertyName,
|
|
395
|
+
statements: [`return this.locatorByTestId("${formattedDataTestId}");`]
|
|
396
|
+
})
|
|
397
|
+
];
|
|
260
398
|
}
|
|
261
399
|
function generateNavigationMethod(args) {
|
|
262
400
|
const { targetPageObjectModelClass: target, baseMethodName, formattedDataTestId, alternateFormattedDataTestIds, params } = args;
|
|
263
401
|
const methodName = baseMethodName ? `goTo${upperFirst$1(baseMethodName)}` : `goTo${target.endsWith("Page") ? target.slice(0, -"Page".length) : target}`;
|
|
264
|
-
const
|
|
402
|
+
const parameters = createParameters(params);
|
|
265
403
|
const alternates = uniqueAlternates(formattedDataTestId, alternateFormattedDataTestIds);
|
|
266
404
|
const candidatesExpr = [formattedDataTestId, ...alternates].map(testIdExpression).join(", ");
|
|
267
405
|
if (alternates.length > 0) {
|
|
268
|
-
return
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
${
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
${
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
406
|
+
return [
|
|
407
|
+
createClassMethod({
|
|
408
|
+
name: methodName,
|
|
409
|
+
parameters,
|
|
410
|
+
returnType: `Fluent<${target}>`,
|
|
411
|
+
statements: (writer) => {
|
|
412
|
+
writer.write("return this.fluent(async () => ").block(() => {
|
|
413
|
+
writer.writeLine(`const candidates = [${candidatesExpr}] as const;`);
|
|
414
|
+
writer.writeLine("let lastError: unknown;");
|
|
415
|
+
writer.write("for (const testId of candidates) ").block(() => {
|
|
416
|
+
writer.writeLine("const locator = this.locatorByTestId(testId);");
|
|
417
|
+
writer.write("try ").block(() => {
|
|
418
|
+
writer.write("if (await locator.count()) ").block(() => {
|
|
419
|
+
writer.writeLine("await this.clickLocator(locator);");
|
|
420
|
+
writer.writeLine(`return new ${target}(this.page);`);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
writer.write("catch (e) ").block(() => {
|
|
424
|
+
writer.writeLine("lastError = e;");
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
writer.writeLine(`throw (lastError instanceof Error) ? lastError : new Error("[pom] Failed to navigate using any candidate locator for ${methodName}.");`);
|
|
428
|
+
});
|
|
429
|
+
writer.writeLine(");");
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
];
|
|
287
433
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
434
|
+
return [
|
|
435
|
+
createClassMethod({
|
|
436
|
+
name: methodName,
|
|
437
|
+
parameters,
|
|
438
|
+
returnType: `Fluent<${target}>`,
|
|
439
|
+
statements: (writer) => {
|
|
440
|
+
writer.write("return this.fluent(async () => ").block(() => {
|
|
441
|
+
writer.writeLine(`await this.clickByTestId(\`${formattedDataTestId}\`);`);
|
|
442
|
+
writer.writeLine(`return new ${target}(this.page);`);
|
|
443
|
+
});
|
|
444
|
+
writer.writeLine(");");
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
];
|
|
296
448
|
}
|
|
297
|
-
function
|
|
449
|
+
function generateViewObjectModelMembers(targetPageObjectModelClass, methodName, nativeRole, formattedDataTestId, alternateFormattedDataTestIds, getterNameOverride, params) {
|
|
298
450
|
const baseMethodName = nativeRole === "radio" ? methodName || "Radio" : methodName;
|
|
299
|
-
const
|
|
451
|
+
const members = generateGetElementByDataTestId(
|
|
452
|
+
baseMethodName,
|
|
453
|
+
nativeRole,
|
|
454
|
+
formattedDataTestId,
|
|
455
|
+
alternateFormattedDataTestIds,
|
|
456
|
+
getterNameOverride,
|
|
457
|
+
params
|
|
458
|
+
);
|
|
300
459
|
if (targetPageObjectModelClass) {
|
|
301
|
-
return
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
460
|
+
return [
|
|
461
|
+
...members,
|
|
462
|
+
...generateNavigationMethod({
|
|
463
|
+
targetPageObjectModelClass,
|
|
464
|
+
baseMethodName,
|
|
465
|
+
formattedDataTestId,
|
|
466
|
+
alternateFormattedDataTestIds,
|
|
467
|
+
params
|
|
468
|
+
})
|
|
469
|
+
];
|
|
308
470
|
}
|
|
309
471
|
if (nativeRole === "select") {
|
|
310
|
-
return
|
|
472
|
+
return [...members, ...generateSelectMethod(baseMethodName, formattedDataTestId)];
|
|
311
473
|
}
|
|
312
474
|
if (nativeRole === "vselect") {
|
|
313
|
-
return
|
|
475
|
+
return [...members, ...generateVSelectMethod(baseMethodName, formattedDataTestId)];
|
|
314
476
|
}
|
|
315
477
|
if (nativeRole === "input") {
|
|
316
|
-
return
|
|
478
|
+
return [...members, ...generateTypeMethod(baseMethodName, formattedDataTestId)];
|
|
317
479
|
}
|
|
318
480
|
if (nativeRole === "radio") {
|
|
319
|
-
return
|
|
481
|
+
return [...members, ...generateRadioMethod(baseMethodName || "Radio", formattedDataTestId)];
|
|
320
482
|
}
|
|
321
|
-
return
|
|
483
|
+
return [...members, ...generateClickMethod(baseMethodName, formattedDataTestId, alternateFormattedDataTestIds, params)];
|
|
322
484
|
}
|
|
323
485
|
function isSimpleExpressionNode(value) {
|
|
324
486
|
return value !== null && "type" in value && value.type === NodeTypes.SIMPLE_EXPRESSION;
|
|
@@ -3227,10 +3389,123 @@ async function parseRouterFileFromCwd(routerEntryPath, options = {}) {
|
|
|
3227
3389
|
}
|
|
3228
3390
|
});
|
|
3229
3391
|
}
|
|
3230
|
-
const AUTO_GENERATED_COMMENT = " * DO NOT MODIFY BY HAND\n *\n * This file is auto-generated by vue-pom-generator.\n * Changes should be made in the generator/template, not in the generated output.\n */";
|
|
3231
3392
|
const GENERATED_GITATTRIBUTES_BLOCK_START = "# BEGIN vue-pom-generator generated files";
|
|
3232
3393
|
const GENERATED_GITATTRIBUTES_BLOCK_END = "# END vue-pom-generator generated files";
|
|
3233
|
-
const
|
|
3394
|
+
const VUE_POM_GENERATOR_ERROR_PREFIX = "[vue-pom-generator]";
|
|
3395
|
+
class VuePomGeneratorError extends Error {
|
|
3396
|
+
constructor(message) {
|
|
3397
|
+
const normalized = message.startsWith(VUE_POM_GENERATOR_ERROR_PREFIX) ? message : `${VUE_POM_GENERATOR_ERROR_PREFIX} ${message}`;
|
|
3398
|
+
super(normalized);
|
|
3399
|
+
this.name = "VuePomGeneratorError";
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
function splitParameterList(parameters) {
|
|
3403
|
+
const parts = [];
|
|
3404
|
+
let current = "";
|
|
3405
|
+
let braceDepth = 0;
|
|
3406
|
+
let bracketDepth = 0;
|
|
3407
|
+
let parenDepth = 0;
|
|
3408
|
+
let angleDepth = 0;
|
|
3409
|
+
let inSingleQuote = false;
|
|
3410
|
+
let inDoubleQuote = false;
|
|
3411
|
+
let inTemplateString = false;
|
|
3412
|
+
for (let index = 0; index < parameters.length; index += 1) {
|
|
3413
|
+
const char = parameters[index];
|
|
3414
|
+
const previous = index > 0 ? parameters[index - 1] : "";
|
|
3415
|
+
if (char === "'" && !inDoubleQuote && !inTemplateString && previous !== "\\") {
|
|
3416
|
+
inSingleQuote = !inSingleQuote;
|
|
3417
|
+
current += char;
|
|
3418
|
+
continue;
|
|
3419
|
+
}
|
|
3420
|
+
if (char === '"' && !inSingleQuote && !inTemplateString && previous !== "\\") {
|
|
3421
|
+
inDoubleQuote = !inDoubleQuote;
|
|
3422
|
+
current += char;
|
|
3423
|
+
continue;
|
|
3424
|
+
}
|
|
3425
|
+
if (char === "`" && !inSingleQuote && !inDoubleQuote && previous !== "\\") {
|
|
3426
|
+
inTemplateString = !inTemplateString;
|
|
3427
|
+
current += char;
|
|
3428
|
+
continue;
|
|
3429
|
+
}
|
|
3430
|
+
if (inSingleQuote || inDoubleQuote || inTemplateString) {
|
|
3431
|
+
current += char;
|
|
3432
|
+
continue;
|
|
3433
|
+
}
|
|
3434
|
+
switch (char) {
|
|
3435
|
+
case "{":
|
|
3436
|
+
braceDepth += 1;
|
|
3437
|
+
break;
|
|
3438
|
+
case "}":
|
|
3439
|
+
braceDepth -= 1;
|
|
3440
|
+
break;
|
|
3441
|
+
case "[":
|
|
3442
|
+
bracketDepth += 1;
|
|
3443
|
+
break;
|
|
3444
|
+
case "]":
|
|
3445
|
+
bracketDepth -= 1;
|
|
3446
|
+
break;
|
|
3447
|
+
case "(":
|
|
3448
|
+
parenDepth += 1;
|
|
3449
|
+
break;
|
|
3450
|
+
case ")":
|
|
3451
|
+
parenDepth -= 1;
|
|
3452
|
+
break;
|
|
3453
|
+
case "<":
|
|
3454
|
+
angleDepth += 1;
|
|
3455
|
+
break;
|
|
3456
|
+
case ">":
|
|
3457
|
+
angleDepth -= 1;
|
|
3458
|
+
break;
|
|
3459
|
+
case ",":
|
|
3460
|
+
if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0 && angleDepth === 0) {
|
|
3461
|
+
const trimmed2 = current.trim();
|
|
3462
|
+
if (trimmed2) {
|
|
3463
|
+
parts.push(trimmed2);
|
|
3464
|
+
}
|
|
3465
|
+
current = "";
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
break;
|
|
3469
|
+
}
|
|
3470
|
+
current += char;
|
|
3471
|
+
}
|
|
3472
|
+
const trimmed = current.trim();
|
|
3473
|
+
if (trimmed) {
|
|
3474
|
+
parts.push(trimmed);
|
|
3475
|
+
}
|
|
3476
|
+
return parts;
|
|
3477
|
+
}
|
|
3478
|
+
function parseParameterSignature(parameter) {
|
|
3479
|
+
const colonIndex = parameter.indexOf(":");
|
|
3480
|
+
if (colonIndex < 0) {
|
|
3481
|
+
return { name: parameter.trim() };
|
|
3482
|
+
}
|
|
3483
|
+
const rawName = parameter.slice(0, colonIndex).trim();
|
|
3484
|
+
const hasQuestionToken = rawName.endsWith("?");
|
|
3485
|
+
const name = hasQuestionToken ? rawName.slice(0, -1).trim() : rawName;
|
|
3486
|
+
const remainder = parameter.slice(colonIndex + 1).trim();
|
|
3487
|
+
const initializerIndex = remainder.lastIndexOf("=");
|
|
3488
|
+
if (initializerIndex < 0) {
|
|
3489
|
+
return {
|
|
3490
|
+
name,
|
|
3491
|
+
hasQuestionToken,
|
|
3492
|
+
type: remainder || void 0
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
return {
|
|
3496
|
+
name,
|
|
3497
|
+
hasQuestionToken,
|
|
3498
|
+
type: remainder.slice(0, initializerIndex).trim() || void 0,
|
|
3499
|
+
initializer: remainder.slice(initializerIndex + 1).trim() || void 0
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
function parseParameterSignatures(parameters) {
|
|
3503
|
+
const trimmed = parameters.trim();
|
|
3504
|
+
if (!trimmed) {
|
|
3505
|
+
return [];
|
|
3506
|
+
}
|
|
3507
|
+
return splitParameterList(trimmed).map(parseParameterSignature);
|
|
3508
|
+
}
|
|
3234
3509
|
function toPosixRelativePath(fromDir, toFile) {
|
|
3235
3510
|
let rel = path.relative(fromDir, toFile).replace(/\\/g, "/");
|
|
3236
3511
|
if (!rel.startsWith(".")) {
|
|
@@ -3238,12 +3513,6 @@ function toPosixRelativePath(fromDir, toFile) {
|
|
|
3238
3513
|
}
|
|
3239
3514
|
return rel;
|
|
3240
3515
|
}
|
|
3241
|
-
function changeExtension(filePath, expectedExt, nextExtWithDot) {
|
|
3242
|
-
const parsed = path.parse(filePath);
|
|
3243
|
-
if (parsed.ext !== expectedExt)
|
|
3244
|
-
return filePath;
|
|
3245
|
-
return path.format({ ...parsed, base: `${parsed.name}${nextExtWithDot}`, ext: nextExtWithDot });
|
|
3246
|
-
}
|
|
3247
3516
|
function stripExtension(filePath) {
|
|
3248
3517
|
const posix = (filePath ?? "").replace(/\\/g, "/");
|
|
3249
3518
|
const parsed = path.posix.parse(posix);
|
|
@@ -3256,6 +3525,70 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
3256
3525
|
const root = projectRoot ?? process.cwd();
|
|
3257
3526
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
3258
3527
|
}
|
|
3528
|
+
function createCustomPomImportCollisionError(exportName, requested) {
|
|
3529
|
+
return new VuePomGeneratorError(
|
|
3530
|
+
`Custom POM import name collision detected for "${exportName}".
|
|
3531
|
+
The identifier "${requested}" conflicts with a generated POM class.
|
|
3532
|
+
Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] to a unique name, or set generation.playwright.customPoms.importNameCollisionBehavior = "alias" to auto-alias collisions.`
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
function normalizeComponentTagToClassName(tag) {
|
|
3536
|
+
const className = toPascalCase(tag);
|
|
3537
|
+
return className || void 0;
|
|
3538
|
+
}
|
|
3539
|
+
function collectReferencedComponentClassNames(nodes, names) {
|
|
3540
|
+
for (const node of nodes) {
|
|
3541
|
+
switch (node.type) {
|
|
3542
|
+
case NodeTypes$1.ELEMENT: {
|
|
3543
|
+
const element = node;
|
|
3544
|
+
if (element.tagType === ElementTypes.COMPONENT) {
|
|
3545
|
+
const className = normalizeComponentTagToClassName(element.tag);
|
|
3546
|
+
if (className) {
|
|
3547
|
+
names.add(className);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
collectReferencedComponentClassNames(element.children, names);
|
|
3551
|
+
break;
|
|
3552
|
+
}
|
|
3553
|
+
case NodeTypes$1.IF: {
|
|
3554
|
+
const ifNode = node;
|
|
3555
|
+
for (const branch of ifNode.branches) {
|
|
3556
|
+
collectReferencedComponentClassNames(branch.children, names);
|
|
3557
|
+
}
|
|
3558
|
+
break;
|
|
3559
|
+
}
|
|
3560
|
+
case NodeTypes$1.FOR: {
|
|
3561
|
+
const forNode = node;
|
|
3562
|
+
collectReferencedComponentClassNames(forNode.children, names);
|
|
3563
|
+
break;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
function getComponentClassNamesFromVueSource(source) {
|
|
3569
|
+
try {
|
|
3570
|
+
const { descriptor } = parse$1(source);
|
|
3571
|
+
const template = descriptor.template?.content?.trim();
|
|
3572
|
+
if (!template) {
|
|
3573
|
+
return [];
|
|
3574
|
+
}
|
|
3575
|
+
const root = parse$2(template);
|
|
3576
|
+
const names = /* @__PURE__ */ new Set();
|
|
3577
|
+
collectReferencedComponentClassNames(root.children, names);
|
|
3578
|
+
return [...names];
|
|
3579
|
+
} catch {
|
|
3580
|
+
return [];
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
function resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot) {
|
|
3584
|
+
const mapped = vueFilesPathMap.get(targetClassName);
|
|
3585
|
+
const candidates = [
|
|
3586
|
+
mapped,
|
|
3587
|
+
path.join(projectRoot, "src", "views", `${targetClassName}.vue`),
|
|
3588
|
+
path.join(projectRoot, "src", "components", `${targetClassName}.vue`)
|
|
3589
|
+
].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
|
|
3590
|
+
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
3591
|
+
}
|
|
3259
3592
|
async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
|
|
3260
3593
|
const root = projectRoot ?? process.cwd();
|
|
3261
3594
|
const viewsDir = options.viewsDir ?? "src/views";
|
|
@@ -3290,35 +3623,40 @@ async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, opt
|
|
|
3290
3623
|
);
|
|
3291
3624
|
}
|
|
3292
3625
|
function generateRouteProperty(routeMeta) {
|
|
3293
|
-
if (!routeMeta) {
|
|
3294
|
-
return " static readonly route: { template: string } | null = null;\n";
|
|
3295
|
-
}
|
|
3296
3626
|
return [
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3627
|
+
createClassProperty({
|
|
3628
|
+
name: "route",
|
|
3629
|
+
isStatic: true,
|
|
3630
|
+
isReadonly: true,
|
|
3631
|
+
type: "{ template: string } | null",
|
|
3632
|
+
initializer: routeMeta ? `{ template: ${JSON.stringify(routeMeta.template)} } as const` : "null"
|
|
3633
|
+
})
|
|
3634
|
+
];
|
|
3302
3635
|
}
|
|
3303
3636
|
function generateGoToSelfMethod(componentName) {
|
|
3304
3637
|
return [
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
"
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3638
|
+
createClassMethod({
|
|
3639
|
+
name: "goTo",
|
|
3640
|
+
isAsync: true,
|
|
3641
|
+
statements: [
|
|
3642
|
+
"await this.goToSelf();"
|
|
3643
|
+
]
|
|
3644
|
+
}),
|
|
3645
|
+
createClassMethod({
|
|
3646
|
+
name: "goToSelf",
|
|
3647
|
+
isAsync: true,
|
|
3648
|
+
statements: [
|
|
3649
|
+
`const route = ${componentName}.route;`,
|
|
3650
|
+
"if (!route) {",
|
|
3651
|
+
` throw new Error("[pom] No router path found for component/page-object '${componentName}'.");`,
|
|
3652
|
+
"}",
|
|
3653
|
+
"const runtimeEnv = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;",
|
|
3654
|
+
"const runtimeBaseUrl = runtimeEnv?.PLAYWRIGHT_RUNTIME_BASE_URL ?? runtimeEnv?.PLAYWRIGHT_TEST_BASE_URL ?? runtimeEnv?.VITE_PLAYWRIGHT_BASE_URL;",
|
|
3655
|
+
"const targetUrl = runtimeBaseUrl ? new URL(route.template, runtimeBaseUrl).toString() : route.template;",
|
|
3656
|
+
"await this.page.goto(targetUrl);"
|
|
3657
|
+
]
|
|
3658
|
+
})
|
|
3659
|
+
];
|
|
3322
3660
|
}
|
|
3323
3661
|
function formatMethodParams(params) {
|
|
3324
3662
|
if (!params)
|
|
@@ -3333,21 +3671,13 @@ function formatMethodParams(params) {
|
|
|
3333
3671
|
};
|
|
3334
3672
|
return entries.slice().sort((a, b) => score(a[0]) - score(b[0]) || a[0].localeCompare(b[0])).map(([name, typeExpr]) => `${name}: ${typeExpr}`).join(", ");
|
|
3335
3673
|
}
|
|
3336
|
-
function
|
|
3674
|
+
function generateExtraClickMethodMembers(spec) {
|
|
3337
3675
|
if (spec.kind !== "click") {
|
|
3338
|
-
return
|
|
3676
|
+
return [];
|
|
3339
3677
|
}
|
|
3340
3678
|
const params = spec.params ?? {};
|
|
3341
3679
|
const signatureParams = formatMethodParams(params);
|
|
3342
|
-
const
|
|
3343
|
-
const lines = [];
|
|
3344
|
-
lines.push(
|
|
3345
|
-
"",
|
|
3346
|
-
` async ${spec.name}${signature} {`
|
|
3347
|
-
);
|
|
3348
|
-
if (spec.keyLiteral !== void 0) {
|
|
3349
|
-
lines.push(` const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
3350
|
-
}
|
|
3680
|
+
const parameters = parseParameterSignatures(signatureParams);
|
|
3351
3681
|
const hasAnnotationText = Object.prototype.hasOwnProperty.call(params, "annotationText");
|
|
3352
3682
|
const hasWait = Object.prototype.hasOwnProperty.call(params, "wait");
|
|
3353
3683
|
const annotationArg = hasAnnotationText ? "annotationText" : '""';
|
|
@@ -3355,9 +3685,6 @@ function generateExtraClickMethodContent(spec) {
|
|
|
3355
3685
|
if (spec.selector.kind === "testId") {
|
|
3356
3686
|
const needsTemplate = spec.selector.formattedDataTestId.includes("${");
|
|
3357
3687
|
const testIdExpr = needsTemplate ? `\`${spec.selector.formattedDataTestId}\`` : JSON.stringify(spec.selector.formattedDataTestId);
|
|
3358
|
-
if (needsTemplate) {
|
|
3359
|
-
lines.push(` const testId = ${testIdExpr};`);
|
|
3360
|
-
}
|
|
3361
3688
|
const clickArgs = [];
|
|
3362
3689
|
clickArgs.push(needsTemplate ? "testId" : testIdExpr);
|
|
3363
3690
|
if (hasAnnotationText || hasWait) {
|
|
@@ -3366,33 +3693,54 @@ function generateExtraClickMethodContent(spec) {
|
|
|
3366
3693
|
if (hasWait) {
|
|
3367
3694
|
clickArgs.push(waitArg);
|
|
3368
3695
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3696
|
+
return [
|
|
3697
|
+
createClassMethod({
|
|
3698
|
+
name: spec.name,
|
|
3699
|
+
isAsync: true,
|
|
3700
|
+
parameters,
|
|
3701
|
+
statements: (writer) => {
|
|
3702
|
+
if (spec.keyLiteral !== void 0) {
|
|
3703
|
+
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
3704
|
+
}
|
|
3705
|
+
if (needsTemplate) {
|
|
3706
|
+
writer.writeLine(`const testId = ${testIdExpr};`);
|
|
3707
|
+
}
|
|
3708
|
+
writer.writeLine(`await this.clickByTestId(${clickArgs.join(", ")});`);
|
|
3709
|
+
}
|
|
3710
|
+
})
|
|
3711
|
+
];
|
|
3373
3712
|
}
|
|
3374
3713
|
const rootNeedsTemplate = spec.selector.rootFormattedDataTestId.includes("${");
|
|
3375
3714
|
const labelNeedsTemplate = spec.selector.formattedLabel.includes("${");
|
|
3376
3715
|
const rootExpr = rootNeedsTemplate ? `\`${spec.selector.rootFormattedDataTestId}\`` : JSON.stringify(spec.selector.rootFormattedDataTestId);
|
|
3377
3716
|
const labelExpr = labelNeedsTemplate ? `\`${spec.selector.formattedLabel}\`` : JSON.stringify(spec.selector.formattedLabel);
|
|
3378
|
-
if (rootNeedsTemplate) {
|
|
3379
|
-
lines.push(` const rootTestId = ${rootExpr};`);
|
|
3380
|
-
}
|
|
3381
|
-
if (labelNeedsTemplate) {
|
|
3382
|
-
lines.push(` const label = ${labelExpr};`);
|
|
3383
|
-
}
|
|
3384
3717
|
const rootArg = rootNeedsTemplate ? "rootTestId" : rootExpr;
|
|
3385
3718
|
const labelArg = labelNeedsTemplate ? "label" : labelExpr;
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3719
|
+
return [
|
|
3720
|
+
createClassMethod({
|
|
3721
|
+
name: spec.name,
|
|
3722
|
+
isAsync: true,
|
|
3723
|
+
parameters,
|
|
3724
|
+
statements: (writer) => {
|
|
3725
|
+
if (spec.keyLiteral !== void 0) {
|
|
3726
|
+
writer.writeLine(`const key = ${JSON.stringify(spec.keyLiteral)};`);
|
|
3727
|
+
}
|
|
3728
|
+
if (rootNeedsTemplate) {
|
|
3729
|
+
writer.writeLine(`const rootTestId = ${rootExpr};`);
|
|
3730
|
+
}
|
|
3731
|
+
if (labelNeedsTemplate) {
|
|
3732
|
+
writer.writeLine(`const label = ${labelExpr};`);
|
|
3733
|
+
}
|
|
3734
|
+
writer.writeLine(`await this.clickWithinTestIdByLabel(${rootArg}, ${labelArg}, ${annotationArg}, ${waitArg});`);
|
|
3735
|
+
}
|
|
3736
|
+
})
|
|
3737
|
+
];
|
|
3390
3738
|
}
|
|
3391
|
-
function
|
|
3739
|
+
function generateMethodMembersFromPom(primary, targetPageObjectModelClass) {
|
|
3392
3740
|
if (primary.emitPrimary === false) {
|
|
3393
|
-
return
|
|
3741
|
+
return [];
|
|
3394
3742
|
}
|
|
3395
|
-
return
|
|
3743
|
+
return generateViewObjectModelMembers(
|
|
3396
3744
|
targetPageObjectModelClass,
|
|
3397
3745
|
primary.methodName,
|
|
3398
3746
|
primary.nativeRole,
|
|
@@ -3426,14 +3774,14 @@ function generateMethodsContentForDependencies(dependencies) {
|
|
|
3426
3774
|
return true;
|
|
3427
3775
|
});
|
|
3428
3776
|
const extras = (dependencies.pomExtraMethods ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
3429
|
-
|
|
3777
|
+
const members = [];
|
|
3430
3778
|
for (const { pom, target } of primarySpecs) {
|
|
3431
|
-
|
|
3779
|
+
members.push(...generateMethodMembersFromPom(pom, target));
|
|
3432
3780
|
}
|
|
3433
3781
|
for (const extra of extras) {
|
|
3434
|
-
|
|
3782
|
+
members.push(...generateExtraClickMethodMembers(extra));
|
|
3435
3783
|
}
|
|
3436
|
-
return
|
|
3784
|
+
return members;
|
|
3437
3785
|
}
|
|
3438
3786
|
async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, options = {}) {
|
|
3439
3787
|
const {
|
|
@@ -3446,6 +3794,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
3446
3794
|
customPomImportNameCollisionBehavior = "error",
|
|
3447
3795
|
testIdAttribute,
|
|
3448
3796
|
emitLanguages: emitLanguagesOverride,
|
|
3797
|
+
typescriptOutputStructure = "aggregated",
|
|
3449
3798
|
csharp,
|
|
3450
3799
|
vueRouterFluentChaining,
|
|
3451
3800
|
routerEntry,
|
|
@@ -3467,7 +3816,16 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
3467
3816
|
generatedFilePaths.push(resolvedFilePath);
|
|
3468
3817
|
};
|
|
3469
3818
|
if (emitLanguages.includes("ts")) {
|
|
3470
|
-
const files = await
|
|
3819
|
+
const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
|
|
3820
|
+
customPomAttachments,
|
|
3821
|
+
projectRoot,
|
|
3822
|
+
customPomDir,
|
|
3823
|
+
customPomImportAliases,
|
|
3824
|
+
customPomImportNameCollisionBehavior,
|
|
3825
|
+
testIdAttribute,
|
|
3826
|
+
routeMetaByComponent,
|
|
3827
|
+
vueRouterFluentChaining
|
|
3828
|
+
}) : await generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
|
|
3471
3829
|
customPomAttachments,
|
|
3472
3830
|
projectRoot,
|
|
3473
3831
|
customPomDir,
|
|
@@ -3504,6 +3862,100 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
3504
3862
|
createFile(file.filePath, file.content);
|
|
3505
3863
|
}
|
|
3506
3864
|
}
|
|
3865
|
+
async function generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, options = {}) {
|
|
3866
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3867
|
+
const entries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
3868
|
+
const base = ensureDir(outDir);
|
|
3869
|
+
const generatedClassNames = new Set(entries.map(([name]) => name));
|
|
3870
|
+
const referencedTargets = /* @__PURE__ */ new Set();
|
|
3871
|
+
for (const [, deps] of entries) {
|
|
3872
|
+
for (const dataTestId of deps.dataTestIdSet ?? []) {
|
|
3873
|
+
if (dataTestId.targetPageObjectModelClass) {
|
|
3874
|
+
referencedTargets.add(dataTestId.targetPageObjectModelClass);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
const stubTargets = Array.from(referencedTargets).filter((target) => !generatedClassNames.has(target)).sort((a, b) => a.localeCompare(b));
|
|
3879
|
+
const availableClassNames = /* @__PURE__ */ new Set([...generatedClassNames, ...stubTargets]);
|
|
3880
|
+
const depsByClassName = new Map(entries);
|
|
3881
|
+
const generatedTsFilePathByComponent = /* @__PURE__ */ new Map();
|
|
3882
|
+
for (const className of availableClassNames) {
|
|
3883
|
+
generatedTsFilePathByComponent.set(className, path.join(base, `${className}.g.ts`));
|
|
3884
|
+
}
|
|
3885
|
+
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
3886
|
+
customPomDir: options.customPomDir,
|
|
3887
|
+
customPomImportAliases: options.customPomImportAliases,
|
|
3888
|
+
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
3889
|
+
});
|
|
3890
|
+
const runtimeBasePagePath = path.join(base, "_pom-runtime", "class-generation", "BasePage.ts");
|
|
3891
|
+
const files = [];
|
|
3892
|
+
for (const [name, deps] of entries) {
|
|
3893
|
+
const filePath = generatedTsFilePathByComponent.get(name);
|
|
3894
|
+
if (!filePath) {
|
|
3895
|
+
continue;
|
|
3896
|
+
}
|
|
3897
|
+
const content = generateViewObjectModelContent(name, deps, componentHierarchyMap, vueFilesPathMap, runtimeBasePagePath, {
|
|
3898
|
+
outputDir: path.dirname(filePath),
|
|
3899
|
+
customPomAttachments: options.customPomAttachments ?? [],
|
|
3900
|
+
projectRoot,
|
|
3901
|
+
customPomDir: options.customPomDir,
|
|
3902
|
+
customPomImportAliases: options.customPomImportAliases,
|
|
3903
|
+
customPomClassIdentifierMap: customPomImportResolution.classIdentifierMap,
|
|
3904
|
+
customPomAvailableClassIdentifiers: customPomImportResolution.availableClassIdentifiers,
|
|
3905
|
+
customPomImportSpecifiersByClass: customPomImportResolution.importSpecifiersByClass,
|
|
3906
|
+
customPomMethodSignaturesByClass: customPomImportResolution.methodSignaturesByClass,
|
|
3907
|
+
generatedTsFilePathByComponent,
|
|
3908
|
+
testIdAttribute: options.testIdAttribute,
|
|
3909
|
+
vueRouterFluentChaining: options.vueRouterFluentChaining,
|
|
3910
|
+
routeMetaByComponent: options.routeMetaByComponent
|
|
3911
|
+
});
|
|
3912
|
+
files.push({ filePath, content });
|
|
3913
|
+
}
|
|
3914
|
+
for (const targetClassName of stubTargets) {
|
|
3915
|
+
const filePath = generatedTsFilePathByComponent.get(targetClassName);
|
|
3916
|
+
if (!filePath) {
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3919
|
+
const outputDir = path.dirname(filePath);
|
|
3920
|
+
const basePageImportSpecifier = stripExtension(toPosixRelativePath(outputDir, runtimeBasePagePath));
|
|
3921
|
+
const composed = getComposedStubBody(targetClassName, availableClassNames, depsByClassName, vueFilesPathMap, projectRoot);
|
|
3922
|
+
const childImports = getChildImportSpecifiers(outputDir, composed?.childClassNames ?? [], generatedTsFilePathByComponent);
|
|
3923
|
+
const members = composed?.members ?? getDefaultStubMembers();
|
|
3924
|
+
const content = renderSplitStubPomContent({
|
|
3925
|
+
className: targetClassName,
|
|
3926
|
+
basePageImportSpecifier,
|
|
3927
|
+
childImports,
|
|
3928
|
+
members
|
|
3929
|
+
});
|
|
3930
|
+
files.push({ filePath, content });
|
|
3931
|
+
}
|
|
3932
|
+
const runtimeAssetSpecs = getRuntimeGeneratedAssetSpecs(base, basePageClassPath);
|
|
3933
|
+
const runtimeFiles = buildRuntimeGeneratedFilesFromSpecs(runtimeAssetSpecs);
|
|
3934
|
+
const indexContent = renderSourceFile("index.ts", (sourceFile) => {
|
|
3935
|
+
for (const spec of runtimeAssetSpecs) {
|
|
3936
|
+
addExportAll(sourceFile, stripExtension(toPosixRelativePath(base, spec.outputPath)));
|
|
3937
|
+
}
|
|
3938
|
+
for (const [, filePath] of Array.from(generatedTsFilePathByComponent.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
3939
|
+
addExportAll(sourceFile, `./${stripExtension(path.basename(filePath))}`);
|
|
3940
|
+
}
|
|
3941
|
+
}, {
|
|
3942
|
+
prefixText: buildFilePrefix({
|
|
3943
|
+
eslintDisableSortImports: true,
|
|
3944
|
+
commentLines: [
|
|
3945
|
+
"POM exports",
|
|
3946
|
+
"DO NOT MODIFY BY HAND",
|
|
3947
|
+
"",
|
|
3948
|
+
"This file is auto-generated by vue-pom-generator.",
|
|
3949
|
+
"Changes should be made in the generator/template, not in the generated output."
|
|
3950
|
+
]
|
|
3951
|
+
})
|
|
3952
|
+
});
|
|
3953
|
+
return [
|
|
3954
|
+
...files,
|
|
3955
|
+
{ filePath: path.join(base, "index.ts"), content: indexContent },
|
|
3956
|
+
...runtimeFiles
|
|
3957
|
+
];
|
|
3958
|
+
}
|
|
3507
3959
|
function escapeGitAttributesPattern(value) {
|
|
3508
3960
|
let output = "";
|
|
3509
3961
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -3942,91 +4394,190 @@ function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
|
|
|
3942
4394
|
};
|
|
3943
4395
|
}).filter((entry) => !!entry);
|
|
3944
4396
|
const overrideCtorByClassName = new Map(overrideCtorEntries.map((entry) => [entry.className, entry.localIdentifier]));
|
|
3945
|
-
const overrideImports = overrideCtorEntries.length ? `${overrideCtorEntries.map((entry) => `import { ${entry.className} as ${entry.localIdentifier} } from "${entry.importSpecifier}";`).join("\n")}
|
|
3946
|
-
|
|
3947
|
-
` : "";
|
|
3948
4397
|
const fixtureCtorExpression = (name) => overrideCtorByClassName.get(name) ?? `Pom.${name}`;
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
const
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
}
|
|
3964
|
-
|
|
3965
|
-
`;
|
|
3966
|
-
const fixturesContent = `${header}/** Generated Playwright fixtures (typed page objects). */
|
|
3967
|
-
|
|
3968
|
-
import { expect, test as base } from "@playwright/test";
|
|
3969
|
-
import type { Page as PwPage } from "@playwright/test";
|
|
3970
|
-
import * as Pom from "${pomImport}";
|
|
3971
|
-
${overrideImports}export interface PlaywrightOptions {
|
|
3972
|
-
animation: Pom.PlaywrightAnimationOptions;
|
|
3973
|
-
}
|
|
3974
|
-
|
|
3975
|
-
${pomFactoryType}type PomSetupFixture = { pomSetup: void };
|
|
3976
|
-
type PomFactoryFixture = { pomFactory: PomFactory };
|
|
3977
|
-
|
|
3978
|
-
const pageCtors = {
|
|
3979
|
-
${fixturesTypeEntries}
|
|
3980
|
-
} as const;
|
|
3981
|
-
const componentCtors = {
|
|
3982
|
-
${componentFixturesTypeEntries}
|
|
3983
|
-
} as const;
|
|
3984
|
-
|
|
3985
|
-
export type GeneratedPageFixtures = { [K in keyof typeof pageCtors]: InstanceType<(typeof pageCtors)[K]> };
|
|
3986
|
-
export type GeneratedComponentFixtures = { [K in keyof typeof componentCtors]: InstanceType<(typeof componentCtors)[K]> };
|
|
3987
|
-
|
|
3988
|
-
const makePomFixture = <T>(Ctor: PomConstructor<T>) => async ({ page }: { page: PwPage }, use: (t: T) => Promise<void>) => {
|
|
3989
|
-
await use(new Ctor(page));
|
|
3990
|
-
};
|
|
3991
|
-
|
|
3992
|
-
const createPomFixtures = <TMap extends Record<string, PomConstructor<any>>>(ctors: TMap) => {
|
|
3993
|
-
const out: Record<string, any> = {};
|
|
3994
|
-
for (const [key, Ctor] of Object.entries(ctors)) {
|
|
3995
|
-
out[key] = makePomFixture(Ctor as PomConstructor<any>);
|
|
3996
|
-
}
|
|
3997
|
-
return out as any;
|
|
3998
|
-
};
|
|
3999
|
-
|
|
4000
|
-
const test = base.extend<PlaywrightOptions & PomSetupFixture & PomFactoryFixture & GeneratedPageFixtures & GeneratedComponentFixtures>({
|
|
4001
|
-
animation: [{
|
|
4002
|
-
pointer: { durationMilliseconds: 250, transitionStyle: "ease-in-out", clickDelayMilliseconds: 0 },
|
|
4003
|
-
keyboard: { typeDelayMilliseconds: 100 },
|
|
4004
|
-
}, { option: true }],
|
|
4005
|
-
pomSetup: [async ({ animation }, use) => {
|
|
4006
|
-
Pom.setPlaywrightAnimationOptions(animation);
|
|
4007
|
-
await use();
|
|
4008
|
-
}, { auto: true }],
|
|
4009
|
-
pomFactory: async ({ page }, use) => {
|
|
4010
|
-
await use({
|
|
4011
|
-
create: <T>(ctor: PomConstructor<T>) => new ctor(page),
|
|
4398
|
+
const pageCtorEntries = viewClassNames.map((name) => ({
|
|
4399
|
+
fixtureName: lowerFirst(name),
|
|
4400
|
+
ctorExpression: fixtureCtorExpression(name)
|
|
4401
|
+
}));
|
|
4402
|
+
const componentCtorEntries = componentClassNames.map((name) => ({
|
|
4403
|
+
fixtureName: lowerFirst(name),
|
|
4404
|
+
ctorExpression: fixtureCtorExpression(name)
|
|
4405
|
+
}));
|
|
4406
|
+
const fixturesContent = renderSourceFile(fixtureFileName, (sourceFile) => {
|
|
4407
|
+
sourceFile.addStatements("/** Generated Playwright fixtures (typed page objects). */");
|
|
4408
|
+
addNamedImport(sourceFile, {
|
|
4409
|
+
moduleSpecifier: "@playwright/test",
|
|
4410
|
+
namedImports: [
|
|
4411
|
+
"expect",
|
|
4412
|
+
{ name: "test", alias: "base" }
|
|
4413
|
+
]
|
|
4012
4414
|
});
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4415
|
+
addNamedImport(sourceFile, {
|
|
4416
|
+
moduleSpecifier: "@playwright/test",
|
|
4417
|
+
isTypeOnly: true,
|
|
4418
|
+
namedImports: [{ name: "Page", alias: "PwPage" }]
|
|
4419
|
+
});
|
|
4420
|
+
sourceFile.addImportDeclaration({
|
|
4421
|
+
namespaceImport: "Pom",
|
|
4422
|
+
moduleSpecifier: pomImport
|
|
4423
|
+
});
|
|
4424
|
+
for (const entry of overrideCtorEntries) {
|
|
4425
|
+
addNamedImport(sourceFile, {
|
|
4426
|
+
moduleSpecifier: entry.importSpecifier,
|
|
4427
|
+
namedImports: [{ name: entry.className, alias: entry.localIdentifier }]
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
sourceFile.addInterface({
|
|
4431
|
+
isExported: true,
|
|
4432
|
+
name: "PlaywrightOptions",
|
|
4433
|
+
properties: [{
|
|
4434
|
+
name: "animation",
|
|
4435
|
+
type: "Pom.PlaywrightAnimationOptions"
|
|
4436
|
+
}]
|
|
4437
|
+
});
|
|
4438
|
+
sourceFile.addTypeAlias({
|
|
4439
|
+
isExported: true,
|
|
4440
|
+
name: "PomConstructor",
|
|
4441
|
+
typeParameters: [{ name: "T" }],
|
|
4442
|
+
type: "new (page: PwPage) => T"
|
|
4443
|
+
});
|
|
4444
|
+
sourceFile.addInterface({
|
|
4445
|
+
isExported: true,
|
|
4446
|
+
name: "PomFactory",
|
|
4447
|
+
methods: [{
|
|
4448
|
+
name: "create",
|
|
4449
|
+
typeParameters: [{ name: "T" }],
|
|
4450
|
+
parameters: [{ name: "ctor", type: "PomConstructor<T>" }],
|
|
4451
|
+
returnType: "T"
|
|
4452
|
+
}]
|
|
4453
|
+
});
|
|
4454
|
+
sourceFile.addTypeAlias({
|
|
4455
|
+
name: "PomSetupFixture",
|
|
4456
|
+
type: "{ pomSetup: void }"
|
|
4457
|
+
});
|
|
4458
|
+
sourceFile.addTypeAlias({
|
|
4459
|
+
name: "PomFactoryFixture",
|
|
4460
|
+
type: "{ pomFactory: PomFactory }"
|
|
4461
|
+
});
|
|
4462
|
+
sourceFile.addVariableStatement({
|
|
4463
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
4464
|
+
declarations: [{
|
|
4465
|
+
name: "pageCtors",
|
|
4466
|
+
initializer: (writer) => {
|
|
4467
|
+
writer.write("{").newLine();
|
|
4468
|
+
writer.indent(() => {
|
|
4469
|
+
for (const entry of pageCtorEntries) {
|
|
4470
|
+
writer.writeLine(`${entry.fixtureName}: ${entry.ctorExpression},`);
|
|
4471
|
+
}
|
|
4472
|
+
});
|
|
4473
|
+
writer.write("} as const");
|
|
4474
|
+
}
|
|
4475
|
+
}]
|
|
4476
|
+
});
|
|
4477
|
+
sourceFile.addVariableStatement({
|
|
4478
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
4479
|
+
declarations: [{
|
|
4480
|
+
name: "componentCtors",
|
|
4481
|
+
initializer: (writer) => {
|
|
4482
|
+
writer.write("{").newLine();
|
|
4483
|
+
writer.indent(() => {
|
|
4484
|
+
for (const entry of componentCtorEntries) {
|
|
4485
|
+
writer.writeLine(`${entry.fixtureName}: ${entry.ctorExpression},`);
|
|
4486
|
+
}
|
|
4487
|
+
});
|
|
4488
|
+
writer.write("} as const");
|
|
4489
|
+
}
|
|
4490
|
+
}]
|
|
4491
|
+
});
|
|
4492
|
+
sourceFile.addTypeAlias({
|
|
4493
|
+
isExported: true,
|
|
4494
|
+
name: "GeneratedPageFixtures",
|
|
4495
|
+
type: "{ [K in keyof typeof pageCtors]: InstanceType<(typeof pageCtors)[K]> }"
|
|
4496
|
+
});
|
|
4497
|
+
sourceFile.addTypeAlias({
|
|
4498
|
+
isExported: true,
|
|
4499
|
+
name: "GeneratedComponentFixtures",
|
|
4500
|
+
type: "{ [K in keyof typeof componentCtors]: InstanceType<(typeof componentCtors)[K]> }"
|
|
4501
|
+
});
|
|
4502
|
+
sourceFile.addFunction({
|
|
4503
|
+
name: "makePomFixture",
|
|
4504
|
+
typeParameters: [{ name: "T" }],
|
|
4505
|
+
parameters: [{ name: "Ctor", type: "PomConstructor<T>" }],
|
|
4506
|
+
statements: [
|
|
4507
|
+
"return async ({ page }: { page: PwPage }, use: (t: T) => Promise<void>) => {",
|
|
4508
|
+
" await use(new Ctor(page));",
|
|
4509
|
+
"};"
|
|
4510
|
+
]
|
|
4511
|
+
});
|
|
4512
|
+
sourceFile.addFunction({
|
|
4513
|
+
name: "createPomFixtures",
|
|
4514
|
+
typeParameters: [{ name: "TMap", constraint: "Record<string, PomConstructor<any>>" }],
|
|
4515
|
+
parameters: [{ name: "ctors", type: "TMap" }],
|
|
4516
|
+
statements: [
|
|
4517
|
+
"const out: Record<string, any> = {};",
|
|
4518
|
+
"for (const [key, Ctor] of Object.entries(ctors)) {",
|
|
4519
|
+
" out[key] = makePomFixture(Ctor as PomConstructor<any>);",
|
|
4520
|
+
"}",
|
|
4521
|
+
"return out as any;"
|
|
4522
|
+
]
|
|
4523
|
+
});
|
|
4524
|
+
sourceFile.addVariableStatement({
|
|
4525
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
4526
|
+
declarations: [{
|
|
4527
|
+
name: "test",
|
|
4528
|
+
initializer: (writer) => {
|
|
4529
|
+
writer.write("base.extend<PlaywrightOptions & PomSetupFixture & PomFactoryFixture & GeneratedPageFixtures & GeneratedComponentFixtures>(");
|
|
4530
|
+
writer.block(() => {
|
|
4531
|
+
writer.writeLine("animation: [{");
|
|
4532
|
+
writer.indent(() => {
|
|
4533
|
+
writer.writeLine('pointer: { durationMilliseconds: 250, transitionStyle: "ease-in-out", clickDelayMilliseconds: 0 },');
|
|
4534
|
+
writer.writeLine("keyboard: { typeDelayMilliseconds: 100 },");
|
|
4535
|
+
});
|
|
4536
|
+
writer.writeLine("}, { option: true }],");
|
|
4537
|
+
writer.writeLine("pomSetup: [async ({ animation }, use) => {");
|
|
4538
|
+
writer.indent(() => {
|
|
4539
|
+
writer.writeLine("Pom.setPlaywrightAnimationOptions(animation);");
|
|
4540
|
+
writer.writeLine("await use();");
|
|
4541
|
+
});
|
|
4542
|
+
writer.writeLine("}, { auto: true }],");
|
|
4543
|
+
writer.writeLine("pomFactory: async ({ page }, use) => {");
|
|
4544
|
+
writer.indent(() => {
|
|
4545
|
+
writer.writeLine("await use({");
|
|
4546
|
+
writer.indent(() => {
|
|
4547
|
+
writer.writeLine("create: <T>(ctor: PomConstructor<T>) => new ctor(page),");
|
|
4548
|
+
});
|
|
4549
|
+
writer.writeLine("});");
|
|
4550
|
+
});
|
|
4551
|
+
writer.writeLine("},");
|
|
4552
|
+
writer.writeLine("...createPomFixtures(pageCtors),");
|
|
4553
|
+
writer.writeLine("...createPomFixtures(componentCtors),");
|
|
4554
|
+
});
|
|
4555
|
+
writer.write(")");
|
|
4556
|
+
}
|
|
4557
|
+
}]
|
|
4558
|
+
});
|
|
4559
|
+
sourceFile.addExportDeclaration({
|
|
4560
|
+
namedExports: ["test", "expect"]
|
|
4561
|
+
});
|
|
4562
|
+
}, {
|
|
4563
|
+
prefixText: buildFilePrefix({
|
|
4564
|
+
eslintDisableSortImports: true,
|
|
4565
|
+
commentLines: [
|
|
4566
|
+
"DO NOT MODIFY BY HAND",
|
|
4567
|
+
"",
|
|
4568
|
+
"This file is auto-generated by vue-pom-generator.",
|
|
4569
|
+
"Changes should be made in the generator/template, not in the generated output."
|
|
4570
|
+
]
|
|
4571
|
+
})
|
|
4572
|
+
});
|
|
4573
|
+
return {
|
|
4574
|
+
filePath: path.resolve(fixtureOutDirAbs, fixtureFileName),
|
|
4022
4575
|
content: fixturesContent
|
|
4023
4576
|
};
|
|
4024
4577
|
}
|
|
4025
|
-
function
|
|
4026
|
-
const { isView, childrenComponentSet, usedComponentSet
|
|
4578
|
+
function prepareViewObjectModelClass(componentName, dependencies, componentHierarchyMap, options = {}) {
|
|
4579
|
+
const { isView, childrenComponentSet, usedComponentSet } = dependencies;
|
|
4027
4580
|
const {
|
|
4028
|
-
outputDir = path.dirname(filePath),
|
|
4029
|
-
aggregated = false,
|
|
4030
4581
|
customPomAttachments = [],
|
|
4031
4582
|
testIdAttribute
|
|
4032
4583
|
} = options;
|
|
@@ -4060,45 +4611,9 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
|
|
|
4060
4611
|
flatten: a.flatten ?? false,
|
|
4061
4612
|
methodSignatures: a.flatten ? customPomMethodSignaturesByClass.get(a.className) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map()
|
|
4062
4613
|
}));
|
|
4063
|
-
let content = "";
|
|
4064
|
-
const sourceRel = toPosixRelativePath(outputDir, filePath);
|
|
4065
|
-
const kind = isView ? "Page" : "Component";
|
|
4066
|
-
const doc = `/** ${kind} POM: ${componentName} (source: ${sourceRel}) */
|
|
4067
|
-
`;
|
|
4068
|
-
if (!aggregated) {
|
|
4069
|
-
content = `${eslintSuppressionHeader}${doc}`;
|
|
4070
|
-
if (isView || attachmentsForThisClass.length > 0) {
|
|
4071
|
-
content += 'import type { Page as PwPage } from "@playwright/test";\n';
|
|
4072
|
-
}
|
|
4073
|
-
const projectRoot = options.projectRoot ?? process.cwd();
|
|
4074
|
-
const fromAbs = path.isAbsolute(outputDir) ? outputDir : path.resolve(projectRoot, outputDir);
|
|
4075
|
-
const toAbs = basePageClassPath ? path.isAbsolute(basePageClassPath) ? basePageClassPath : path.resolve(projectRoot, basePageClassPath) : "";
|
|
4076
|
-
const basePageImport = path.relative(fromAbs, toAbs).replace(/\\/g, "/");
|
|
4077
|
-
const basePageImportNoExt = stripExtension(basePageImport).replace(/\\/g, "/");
|
|
4078
|
-
const basePageImportSpecifier = basePageImportNoExt.startsWith(".") ? basePageImportNoExt : `./${basePageImportNoExt}`;
|
|
4079
|
-
content += `import { BasePage, Fluent } from '${basePageImportSpecifier}';
|
|
4080
|
-
|
|
4081
|
-
`;
|
|
4082
|
-
if (isView && childrenComponentSet.size > 0) {
|
|
4083
|
-
childrenComponentSet.forEach((child) => {
|
|
4084
|
-
if (componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size) {
|
|
4085
|
-
const childPath = vueFilesPathMap.get(child);
|
|
4086
|
-
let relativePath = path.relative(outputDir, childPath || "");
|
|
4087
|
-
relativePath = changeExtension(relativePath, ".vue", ".g").replace(/\\/g, "/");
|
|
4088
|
-
content += `import { ${child} } from '${relativePath}';
|
|
4089
|
-
`;
|
|
4090
|
-
}
|
|
4091
|
-
});
|
|
4092
|
-
}
|
|
4093
|
-
} else {
|
|
4094
|
-
content = doc;
|
|
4095
|
-
}
|
|
4096
|
-
const className = toPascalCaseLocal(componentName);
|
|
4097
|
-
content += `
|
|
4098
|
-
export class ${className} extends BasePage {
|
|
4099
|
-
`;
|
|
4100
4614
|
const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
|
|
4101
4615
|
const componentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
|
|
4616
|
+
const className = toPascalCaseLocal(componentName);
|
|
4102
4617
|
const childInstancePropertyNames = Array.from(componentRefsForInstances).filter((child) => componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size).map((child) => child.split(".vue")[0]);
|
|
4103
4618
|
const blockedViewPassthroughMethodNames = new Set(
|
|
4104
4619
|
attachmentsForThisClass.filter((a) => a.flatten).flatMap((a) => Array.from(a.methodSignatures.keys()))
|
|
@@ -4108,26 +4623,144 @@ export class ${className} extends BasePage {
|
|
|
4108
4623
|
...widgetInstances.map((w) => w.propertyName),
|
|
4109
4624
|
...childInstancePropertyNames
|
|
4110
4625
|
]);
|
|
4626
|
+
const members = [];
|
|
4111
4627
|
if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
|
|
4112
|
-
|
|
4113
|
-
|
|
4628
|
+
members.push(...getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances));
|
|
4629
|
+
members.push(getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances, { testIdAttribute }));
|
|
4114
4630
|
}
|
|
4115
4631
|
if (!isView && attachmentsForThisClass.length > 0) {
|
|
4116
|
-
|
|
4117
|
-
|
|
4632
|
+
members.push(...getComponentInstances(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass));
|
|
4633
|
+
members.push(getConstructor(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute }));
|
|
4118
4634
|
}
|
|
4119
|
-
|
|
4635
|
+
members.push(
|
|
4636
|
+
...getAttachmentPassthroughMethods(componentName, dependencies, attachmentsForThisClass, reservedAttachmentPassthroughNames)
|
|
4637
|
+
);
|
|
4120
4638
|
if (isView && componentRefsForInstances.size === 1) {
|
|
4121
|
-
|
|
4639
|
+
members.push(
|
|
4640
|
+
...getViewPassthroughMethods(
|
|
4641
|
+
componentName,
|
|
4642
|
+
dependencies,
|
|
4643
|
+
componentRefsForInstances,
|
|
4644
|
+
componentHierarchyMap,
|
|
4645
|
+
blockedViewPassthroughMethodNames
|
|
4646
|
+
)
|
|
4647
|
+
);
|
|
4122
4648
|
}
|
|
4123
4649
|
if (isView && options.vueRouterFluentChaining) {
|
|
4124
4650
|
const routeMeta = options.routeMetaByComponent?.[componentName] ?? null;
|
|
4125
|
-
|
|
4126
|
-
|
|
4651
|
+
members.push(...generateRouteProperty(routeMeta));
|
|
4652
|
+
members.push(...generateGoToSelfMethod(className));
|
|
4127
4653
|
}
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4654
|
+
members.push(...generateMethodsContentForDependencies(dependencies));
|
|
4655
|
+
return {
|
|
4656
|
+
className,
|
|
4657
|
+
componentRefsForInstances,
|
|
4658
|
+
attachmentsForThisClass,
|
|
4659
|
+
widgetInstances,
|
|
4660
|
+
isView,
|
|
4661
|
+
members
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
function generateViewObjectModelContent(componentName, dependencies, componentHierarchyMap, _vueFilesPathMap, basePageClassPath, options = {}) {
|
|
4665
|
+
const { filePath } = dependencies;
|
|
4666
|
+
const outputDir = options.outputDir ?? path.dirname(filePath);
|
|
4667
|
+
const prepared = prepareViewObjectModelClass(componentName, dependencies, componentHierarchyMap, options);
|
|
4668
|
+
const sourceRel = toPosixRelativePath(outputDir, filePath);
|
|
4669
|
+
const kind = prepared.isView ? "Page" : "Component";
|
|
4670
|
+
const doc = `/** ${kind} POM: ${componentName} (source: ${sourceRel}) */`;
|
|
4671
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
4672
|
+
const fromAbs = path.isAbsolute(outputDir) ? outputDir : path.resolve(projectRoot, outputDir);
|
|
4673
|
+
const toAbs = basePageClassPath ? path.isAbsolute(basePageClassPath) ? basePageClassPath : path.resolve(projectRoot, basePageClassPath) : "";
|
|
4674
|
+
const basePageImport = path.relative(fromAbs, toAbs).replace(/\\/g, "/");
|
|
4675
|
+
const basePageImportNoExt = stripExtension(basePageImport).replace(/\\/g, "/");
|
|
4676
|
+
const basePageImportSpecifier = basePageImportNoExt.startsWith(".") ? basePageImportNoExt : `./${basePageImportNoExt}`;
|
|
4677
|
+
const needsPlaywrightPageImport = prepared.isView || prepared.attachmentsForThisClass.length > 0;
|
|
4678
|
+
const customPomImportSpecifiersByClass = options.customPomImportSpecifiersByClass ?? {};
|
|
4679
|
+
const customImports = Array.from(
|
|
4680
|
+
/* @__PURE__ */ new Set([
|
|
4681
|
+
...prepared.attachmentsForThisClass.map((attachment) => attachment.className),
|
|
4682
|
+
...prepared.widgetInstances.map((widget) => widget.className)
|
|
4683
|
+
])
|
|
4684
|
+
).reduce((imports, localIdentifier) => {
|
|
4685
|
+
const specifier = Object.values(customPomImportSpecifiersByClass).find((spec) => spec.localIdentifier === localIdentifier);
|
|
4686
|
+
if (!specifier) {
|
|
4687
|
+
return imports;
|
|
4688
|
+
}
|
|
4689
|
+
imports.push({
|
|
4690
|
+
moduleSpecifier: stripExtension(toPosixRelativePath(fromAbs, specifier.absolutePath)),
|
|
4691
|
+
name: specifier.exportName,
|
|
4692
|
+
alias: specifier.localIdentifier !== specifier.exportName ? specifier.localIdentifier : void 0
|
|
4693
|
+
});
|
|
4694
|
+
return imports;
|
|
4695
|
+
}, []).sort((a, b) => (a.alias ?? a.name).localeCompare(b.alias ?? b.name));
|
|
4696
|
+
const generatedImports = [];
|
|
4697
|
+
const importedGeneratedClasses = /* @__PURE__ */ new Set();
|
|
4698
|
+
const generatedTsFilePathByComponent = options.generatedTsFilePathByComponent;
|
|
4699
|
+
const addGeneratedImport = (className) => {
|
|
4700
|
+
if (!generatedTsFilePathByComponent || importedGeneratedClasses.has(className) || className === componentName) {
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
const generatedFilePath = generatedTsFilePathByComponent.get(className);
|
|
4704
|
+
if (!generatedFilePath) {
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
generatedImports.push({
|
|
4708
|
+
className,
|
|
4709
|
+
moduleSpecifier: stripExtension(toPosixRelativePath(fromAbs, generatedFilePath))
|
|
4710
|
+
});
|
|
4711
|
+
importedGeneratedClasses.add(className);
|
|
4712
|
+
};
|
|
4713
|
+
for (const child of prepared.componentRefsForInstances) {
|
|
4714
|
+
const childName = child.endsWith(".vue") ? child.slice(0, -4) : child;
|
|
4715
|
+
const childDeps = componentHierarchyMap.get(child) ?? componentHierarchyMap.get(childName);
|
|
4716
|
+
if (childDeps?.dataTestIdSet.size) {
|
|
4717
|
+
addGeneratedImport(childName);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
const targetClassNames = Array.from(
|
|
4721
|
+
new Set(
|
|
4722
|
+
Array.from(dependencies.dataTestIdSet ?? []).map((entry) => entry.targetPageObjectModelClass).filter((target) => typeof target === "string" && target.length > 0)
|
|
4723
|
+
)
|
|
4724
|
+
).sort((a, b) => a.localeCompare(b));
|
|
4725
|
+
for (const targetClassName of targetClassNames) {
|
|
4726
|
+
addGeneratedImport(targetClassName);
|
|
4727
|
+
}
|
|
4728
|
+
generatedImports.sort((a, b) => a.className.localeCompare(b.className));
|
|
4729
|
+
const prefixText = `${buildFilePrefix({ eslintDisableSortImports: true })}${doc}
|
|
4730
|
+
`;
|
|
4731
|
+
return renderSourceFile(`${prepared.className}.ts`, (sourceFile) => {
|
|
4732
|
+
if (needsPlaywrightPageImport) {
|
|
4733
|
+
addNamedImport(sourceFile, {
|
|
4734
|
+
moduleSpecifier: "@playwright/test",
|
|
4735
|
+
isTypeOnly: true,
|
|
4736
|
+
namedImports: [{ name: "Page", alias: "PwPage" }]
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4739
|
+
addNamedImport(sourceFile, {
|
|
4740
|
+
moduleSpecifier: basePageImportSpecifier,
|
|
4741
|
+
namedImports: ["BasePage", "Fluent"]
|
|
4742
|
+
});
|
|
4743
|
+
for (const customImport of customImports) {
|
|
4744
|
+
addNamedImport(sourceFile, {
|
|
4745
|
+
moduleSpecifier: customImport.moduleSpecifier,
|
|
4746
|
+
namedImports: [{ name: customImport.name, alias: customImport.alias }]
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
for (const generatedImport of generatedImports) {
|
|
4750
|
+
addNamedImport(sourceFile, {
|
|
4751
|
+
moduleSpecifier: generatedImport.moduleSpecifier,
|
|
4752
|
+
namedImports: [generatedImport.className]
|
|
4753
|
+
});
|
|
4754
|
+
}
|
|
4755
|
+
const classDeclaration = sourceFile.addClass({
|
|
4756
|
+
name: prepared.className,
|
|
4757
|
+
isExported: true,
|
|
4758
|
+
extends: "BasePage"
|
|
4759
|
+
});
|
|
4760
|
+
for (const member of prepared.members) {
|
|
4761
|
+
addClassMember(classDeclaration, member);
|
|
4762
|
+
}
|
|
4763
|
+
}, { prefixText });
|
|
4131
4764
|
}
|
|
4132
4765
|
function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap, blockedMethodNames = /* @__PURE__ */ new Set()) {
|
|
4133
4766
|
const existingOnView = viewDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
|
|
@@ -4151,32 +4784,26 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
4151
4784
|
}
|
|
4152
4785
|
}
|
|
4153
4786
|
const sorted = Array.from(methodToChildren.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
4154
|
-
const
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4787
|
+
const passthroughs = sorted.filter(([, candidates]) => candidates.length === 1);
|
|
4788
|
+
if (!passthroughs.length) {
|
|
4789
|
+
return [];
|
|
4790
|
+
}
|
|
4791
|
+
return passthroughs.map(([methodName, candidates]) => {
|
|
4158
4792
|
const { childProp, params, argNames } = candidates[0];
|
|
4159
4793
|
const callArgs = argNames.join(", ");
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
}
|
|
4170
|
-
return [
|
|
4171
|
-
"",
|
|
4172
|
-
` // Passthrough methods composed from child component POMs of ${viewName}.`,
|
|
4173
|
-
...lines,
|
|
4174
|
-
""
|
|
4175
|
-
].join("\n");
|
|
4794
|
+
return createClassMethod({
|
|
4795
|
+
name: methodName,
|
|
4796
|
+
isAsync: true,
|
|
4797
|
+
parameters: parseParameterSignatures(params),
|
|
4798
|
+
statements: [
|
|
4799
|
+
`return await this.${childProp}.${methodName}(${callArgs});`
|
|
4800
|
+
]
|
|
4801
|
+
});
|
|
4802
|
+
});
|
|
4176
4803
|
}
|
|
4177
4804
|
function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmentsForThisClass, reservedMemberNames) {
|
|
4178
4805
|
if (!attachmentsForThisClass.some((a) => a.flatten && a.methodSignatures.size > 0)) {
|
|
4179
|
-
return
|
|
4806
|
+
return [];
|
|
4180
4807
|
}
|
|
4181
4808
|
const existingOnClass = ownerDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
|
|
4182
4809
|
const methodToAttachments = /* @__PURE__ */ new Map();
|
|
@@ -4198,30 +4825,22 @@ function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmen
|
|
|
4198
4825
|
}
|
|
4199
4826
|
}
|
|
4200
4827
|
const sorted = Array.from(methodToAttachments.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
4201
|
-
const
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4828
|
+
const passthroughs = sorted.filter(([, candidates]) => candidates.length === 1);
|
|
4829
|
+
if (!passthroughs.length) {
|
|
4830
|
+
return [];
|
|
4831
|
+
}
|
|
4832
|
+
return passthroughs.map(([methodName, candidates]) => {
|
|
4206
4833
|
const { propertyName, params, argNames } = candidates[0];
|
|
4207
4834
|
const callArgs = argNames.join(", ");
|
|
4208
4835
|
const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
return "";
|
|
4218
|
-
}
|
|
4219
|
-
return [
|
|
4220
|
-
"",
|
|
4221
|
-
` // Passthrough methods composed from custom helper attachments of ${ownerName}.`,
|
|
4222
|
-
...lines,
|
|
4223
|
-
""
|
|
4224
|
-
].join("\n");
|
|
4836
|
+
return createClassMethod({
|
|
4837
|
+
name: methodName,
|
|
4838
|
+
parameters: parseParameterSignatures(params),
|
|
4839
|
+
statements: [
|
|
4840
|
+
`return ${invocation};`
|
|
4841
|
+
]
|
|
4842
|
+
});
|
|
4843
|
+
});
|
|
4225
4844
|
}
|
|
4226
4845
|
function sliceNodeSource(source, node) {
|
|
4227
4846
|
if (node.start == null || node.end == null) {
|
|
@@ -4305,12 +4924,292 @@ function ensureDir(dir) {
|
|
|
4305
4924
|
}
|
|
4306
4925
|
return normalized;
|
|
4307
4926
|
}
|
|
4927
|
+
function resolvePluginAsset(relative) {
|
|
4928
|
+
try {
|
|
4929
|
+
return fileURLToPath(new URL(relative, import.meta.url));
|
|
4930
|
+
} catch {
|
|
4931
|
+
return path.resolve(__dirname, relative);
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
function readTextAsset(absPath, description) {
|
|
4935
|
+
try {
|
|
4936
|
+
return fs.readFileSync(absPath, "utf8");
|
|
4937
|
+
} catch {
|
|
4938
|
+
throw new VuePomGeneratorError(`Failed to read ${description} at ${absPath}`);
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
function getDefaultStubMembers() {
|
|
4942
|
+
return [
|
|
4943
|
+
createClassConstructor({
|
|
4944
|
+
parameters: [{ name: "page", type: "PwPage" }],
|
|
4945
|
+
statements: [
|
|
4946
|
+
"super(page);"
|
|
4947
|
+
]
|
|
4948
|
+
})
|
|
4949
|
+
];
|
|
4950
|
+
}
|
|
4951
|
+
function renderSplitStubPomContent(options) {
|
|
4952
|
+
const prefixText = buildFilePrefix({
|
|
4953
|
+
eslintDisableSortImports: true,
|
|
4954
|
+
commentLines: [
|
|
4955
|
+
`Stub POM: ${options.className}`,
|
|
4956
|
+
"DO NOT MODIFY BY HAND",
|
|
4957
|
+
"",
|
|
4958
|
+
"This file is auto-generated by vue-pom-generator.",
|
|
4959
|
+
"Changes should be made in the generator/template, not in the generated output."
|
|
4960
|
+
]
|
|
4961
|
+
});
|
|
4962
|
+
return renderSourceFile(`${options.className}.ts`, (sourceFile) => {
|
|
4963
|
+
addNamedImport(sourceFile, {
|
|
4964
|
+
moduleSpecifier: "@playwright/test",
|
|
4965
|
+
isTypeOnly: true,
|
|
4966
|
+
namedImports: [{ name: "Page", alias: "PwPage" }]
|
|
4967
|
+
});
|
|
4968
|
+
addNamedImport(sourceFile, {
|
|
4969
|
+
moduleSpecifier: options.basePageImportSpecifier,
|
|
4970
|
+
namedImports: ["BasePage"]
|
|
4971
|
+
});
|
|
4972
|
+
for (const childImport of options.childImports) {
|
|
4973
|
+
addNamedImport(sourceFile, {
|
|
4974
|
+
moduleSpecifier: childImport.importPath,
|
|
4975
|
+
namedImports: [childImport.className]
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
sourceFile.addStatements(buildCommentBlock([
|
|
4979
|
+
"Stub POM generated because it is referenced as a navigation target but",
|
|
4980
|
+
"did not have any generated test ids in this build."
|
|
4981
|
+
]).trimEnd());
|
|
4982
|
+
const classDeclaration = sourceFile.addClass({
|
|
4983
|
+
name: options.className,
|
|
4984
|
+
isExported: true,
|
|
4985
|
+
extends: "BasePage"
|
|
4986
|
+
});
|
|
4987
|
+
for (const member of options.members) {
|
|
4988
|
+
addClassMember(classDeclaration, member);
|
|
4989
|
+
}
|
|
4990
|
+
}, { prefixText });
|
|
4991
|
+
}
|
|
4992
|
+
function getChildImportSpecifiers(outputDir, childClassNames, generatedTsFilePathByComponent) {
|
|
4993
|
+
return childClassNames.map((childClassName) => {
|
|
4994
|
+
const childFilePath = generatedTsFilePathByComponent.get(childClassName);
|
|
4995
|
+
if (!childFilePath) {
|
|
4996
|
+
return null;
|
|
4997
|
+
}
|
|
4998
|
+
return {
|
|
4999
|
+
className: childClassName,
|
|
5000
|
+
importPath: stripExtension(toPosixRelativePath(outputDir, childFilePath))
|
|
5001
|
+
};
|
|
5002
|
+
}).filter((entry) => !!entry).sort((a, b) => a.className.localeCompare(b.className));
|
|
5003
|
+
}
|
|
5004
|
+
function isConstructorMember(member) {
|
|
5005
|
+
return member.kind === StructureKind.Constructor;
|
|
5006
|
+
}
|
|
5007
|
+
function isGetterMember(member) {
|
|
5008
|
+
return member.kind === StructureKind.GetAccessor;
|
|
5009
|
+
}
|
|
5010
|
+
function isMethodMember(member) {
|
|
5011
|
+
return member.kind === StructureKind.Method;
|
|
5012
|
+
}
|
|
5013
|
+
function isPropertyMember(member) {
|
|
5014
|
+
return member.kind === StructureKind.Property;
|
|
5015
|
+
}
|
|
5016
|
+
function addClassMember(classDeclaration, member) {
|
|
5017
|
+
if (isConstructorMember(member)) {
|
|
5018
|
+
classDeclaration.addConstructor(member);
|
|
5019
|
+
return;
|
|
5020
|
+
}
|
|
5021
|
+
if (isGetterMember(member)) {
|
|
5022
|
+
classDeclaration.addGetAccessor(member);
|
|
5023
|
+
return;
|
|
5024
|
+
}
|
|
5025
|
+
if (isMethodMember(member)) {
|
|
5026
|
+
classDeclaration.addMethod(member);
|
|
5027
|
+
return;
|
|
5028
|
+
}
|
|
5029
|
+
if (isPropertyMember(member)) {
|
|
5030
|
+
classDeclaration.addProperty(member);
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
throw new Error(`Unsupported class member structure: ${String(member)}`);
|
|
5034
|
+
}
|
|
5035
|
+
function getRuntimeGeneratedAssetSpecs(baseDir, basePageClassPath) {
|
|
5036
|
+
const runtimeDirAbs = path.join(baseDir, "_pom-runtime");
|
|
5037
|
+
const runtimeClassGenAbs = path.join(runtimeDirAbs, "class-generation");
|
|
5038
|
+
const runtimeClassGenSourceDir = resolvePluginAsset("../class-generation");
|
|
5039
|
+
const runtimeClassGenFiles = fs.readdirSync(runtimeClassGenSourceDir).filter((file) => file.endsWith(".ts")).filter((file) => file !== "BasePage.ts" && file !== "index.ts").sort((left, right) => left.localeCompare(right));
|
|
5040
|
+
return [
|
|
5041
|
+
{
|
|
5042
|
+
absolutePath: resolvePluginAsset("../click-instrumentation.ts"),
|
|
5043
|
+
description: "click-instrumentation.ts",
|
|
5044
|
+
outputPath: path.join(runtimeDirAbs, "click-instrumentation.ts")
|
|
5045
|
+
},
|
|
5046
|
+
...runtimeClassGenFiles.map((file) => ({
|
|
5047
|
+
absolutePath: path.join(runtimeClassGenSourceDir, file),
|
|
5048
|
+
description: file,
|
|
5049
|
+
outputPath: path.join(runtimeClassGenAbs, file)
|
|
5050
|
+
})),
|
|
5051
|
+
{
|
|
5052
|
+
absolutePath: basePageClassPath,
|
|
5053
|
+
description: "BasePage.ts",
|
|
5054
|
+
outputPath: path.join(runtimeClassGenAbs, "BasePage.ts")
|
|
5055
|
+
}
|
|
5056
|
+
];
|
|
5057
|
+
}
|
|
5058
|
+
function buildRuntimeGeneratedFiles(baseDir, basePageClassPath) {
|
|
5059
|
+
return buildRuntimeGeneratedFilesFromSpecs(getRuntimeGeneratedAssetSpecs(baseDir, basePageClassPath));
|
|
5060
|
+
}
|
|
5061
|
+
function buildRuntimeGeneratedFilesFromSpecs(assetSpecs) {
|
|
5062
|
+
return assetSpecs.map((spec) => ({
|
|
5063
|
+
filePath: spec.outputPath,
|
|
5064
|
+
content: readTextAsset(spec.absolutePath, spec.description)
|
|
5065
|
+
}));
|
|
5066
|
+
}
|
|
5067
|
+
function resolveCustomPomImportResolution(generatedClassNames, projectRoot, options = {}) {
|
|
5068
|
+
const importAliases = {
|
|
5069
|
+
Toggle: "ToggleWidget",
|
|
5070
|
+
Checkbox: "CheckboxWidget",
|
|
5071
|
+
...options.customPomImportAliases
|
|
5072
|
+
};
|
|
5073
|
+
const importCollisionBehavior = options.customPomImportNameCollisionBehavior ?? "error";
|
|
5074
|
+
const reservedIdentifiers = /* @__PURE__ */ new Set([
|
|
5075
|
+
"PwLocator",
|
|
5076
|
+
"PwPage",
|
|
5077
|
+
"BasePage",
|
|
5078
|
+
"Fluent",
|
|
5079
|
+
...generatedClassNames
|
|
5080
|
+
]);
|
|
5081
|
+
const usedImportIdentifiers = /* @__PURE__ */ new Set();
|
|
5082
|
+
const classIdentifierMap = {};
|
|
5083
|
+
const methodSignaturesByClass = /* @__PURE__ */ new Map();
|
|
5084
|
+
const importSpecifiersByClass = {};
|
|
5085
|
+
const ensureUniqueIdentifier = (base) => {
|
|
5086
|
+
let candidate = base;
|
|
5087
|
+
let i = 2;
|
|
5088
|
+
while (reservedIdentifiers.has(candidate) || usedImportIdentifiers.has(candidate)) {
|
|
5089
|
+
candidate = `${base}${i}`;
|
|
5090
|
+
i++;
|
|
5091
|
+
}
|
|
5092
|
+
usedImportIdentifiers.add(candidate);
|
|
5093
|
+
return candidate;
|
|
5094
|
+
};
|
|
5095
|
+
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
5096
|
+
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
5097
|
+
if (!fs.existsSync(customDirAbs)) {
|
|
5098
|
+
return {
|
|
5099
|
+
classIdentifierMap,
|
|
5100
|
+
methodSignaturesByClass,
|
|
5101
|
+
availableClassIdentifiers: /* @__PURE__ */ new Set(),
|
|
5102
|
+
importSpecifiersByClass
|
|
5103
|
+
};
|
|
5104
|
+
}
|
|
5105
|
+
const files = fs.readdirSync(customDirAbs).filter((f) => f.endsWith(".ts")).sort((a, b) => a.localeCompare(b));
|
|
5106
|
+
for (const file of files) {
|
|
5107
|
+
const exportName = file.replace(/\.ts$/i, "");
|
|
5108
|
+
const requested = importAliases[exportName] ?? exportName;
|
|
5109
|
+
const collidesWithGeneratedClass = generatedClassNames.has(requested);
|
|
5110
|
+
const explicitAliasProvided = Object.prototype.hasOwnProperty.call(importAliases, exportName);
|
|
5111
|
+
if (collidesWithGeneratedClass && importCollisionBehavior === "error") {
|
|
5112
|
+
throw createCustomPomImportCollisionError(exportName, requested);
|
|
5113
|
+
}
|
|
5114
|
+
let localIdentifier = requested;
|
|
5115
|
+
if (collidesWithGeneratedClass && importCollisionBehavior === "alias") {
|
|
5116
|
+
const aliasBase = explicitAliasProvided ? requested : `${exportName}Custom`;
|
|
5117
|
+
localIdentifier = ensureUniqueIdentifier(aliasBase);
|
|
5118
|
+
} else {
|
|
5119
|
+
localIdentifier = ensureUniqueIdentifier(requested);
|
|
5120
|
+
}
|
|
5121
|
+
const customFileAbs = path.join(customDirAbs, file);
|
|
5122
|
+
classIdentifierMap[exportName] = localIdentifier;
|
|
5123
|
+
importSpecifiersByClass[exportName] = {
|
|
5124
|
+
exportName,
|
|
5125
|
+
localIdentifier,
|
|
5126
|
+
absolutePath: customFileAbs
|
|
5127
|
+
};
|
|
5128
|
+
const customPomMethodSignatures = extractCustomPomMethodSignatures(fs.readFileSync(customFileAbs, "utf8"), exportName);
|
|
5129
|
+
if (customPomMethodSignatures.size > 0) {
|
|
5130
|
+
methodSignaturesByClass.set(exportName, customPomMethodSignatures);
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
return {
|
|
5134
|
+
classIdentifierMap,
|
|
5135
|
+
methodSignaturesByClass,
|
|
5136
|
+
availableClassIdentifiers: new Set(Object.values(classIdentifierMap)),
|
|
5137
|
+
importSpecifiersByClass
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
function getComposedStubBody(targetClassName, availableClassNames, depsByClassName, vueFilesPathMap, projectRoot) {
|
|
5141
|
+
const filePath = resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot);
|
|
5142
|
+
if (!filePath)
|
|
5143
|
+
return void 0;
|
|
5144
|
+
let source = "";
|
|
5145
|
+
try {
|
|
5146
|
+
source = fs.readFileSync(filePath, "utf8");
|
|
5147
|
+
} catch {
|
|
5148
|
+
return void 0;
|
|
5149
|
+
}
|
|
5150
|
+
const tags = getComponentClassNamesFromVueSource(source);
|
|
5151
|
+
const childClassNames = Array.from(
|
|
5152
|
+
new Set(
|
|
5153
|
+
tags.filter((name) => availableClassNames.has(name)).filter((name) => name !== targetClassName)
|
|
5154
|
+
)
|
|
5155
|
+
).sort((a, b) => a.localeCompare(b));
|
|
5156
|
+
if (!childClassNames.length)
|
|
5157
|
+
return void 0;
|
|
5158
|
+
const methodToChildren = /* @__PURE__ */ new Map();
|
|
5159
|
+
for (const child of childClassNames) {
|
|
5160
|
+
const childDeps = depsByClassName.get(child);
|
|
5161
|
+
const methods = childDeps?.generatedMethods;
|
|
5162
|
+
if (!methods)
|
|
5163
|
+
continue;
|
|
5164
|
+
for (const [name, sig] of methods.entries()) {
|
|
5165
|
+
if (!sig)
|
|
5166
|
+
continue;
|
|
5167
|
+
const list = methodToChildren.get(name) ?? [];
|
|
5168
|
+
list.push({ child, params: sig.params, argNames: sig.argNames });
|
|
5169
|
+
methodToChildren.set(name, list);
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
const passthroughMembers = [];
|
|
5173
|
+
for (const [methodName, candidatesForMethod] of methodToChildren.entries()) {
|
|
5174
|
+
if (candidatesForMethod.length !== 1 || methodName === "constructor")
|
|
5175
|
+
continue;
|
|
5176
|
+
const { child, params, argNames } = candidatesForMethod[0];
|
|
5177
|
+
const callArgs = argNames.join(", ");
|
|
5178
|
+
passthroughMembers.push(createClassMethod({
|
|
5179
|
+
name: methodName,
|
|
5180
|
+
isAsync: true,
|
|
5181
|
+
parameters: parseParameterSignatures(params),
|
|
5182
|
+
statements: [
|
|
5183
|
+
`return await this.${child}.${methodName}(${callArgs});`
|
|
5184
|
+
]
|
|
5185
|
+
}));
|
|
5186
|
+
}
|
|
5187
|
+
return {
|
|
5188
|
+
childClassNames,
|
|
5189
|
+
members: [
|
|
5190
|
+
...childClassNames.map((childClassName) => createClassProperty({
|
|
5191
|
+
name: childClassName,
|
|
5192
|
+
type: childClassName
|
|
5193
|
+
})),
|
|
5194
|
+
createClassConstructor({
|
|
5195
|
+
parameters: [{ name: "page", type: "PwPage" }],
|
|
5196
|
+
statements: (writer) => {
|
|
5197
|
+
writer.writeLine("super(page);");
|
|
5198
|
+
for (const childClassName of childClassNames) {
|
|
5199
|
+
writer.writeLine(`this.${childClassName} = new ${childClassName}(page);`);
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
}),
|
|
5203
|
+
...passthroughMembers
|
|
5204
|
+
]
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
4308
5207
|
async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, options = {}) {
|
|
4309
5208
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
4310
5209
|
const entries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
4311
5210
|
const views = entries.filter(([, d]) => d.isView);
|
|
4312
5211
|
const components = entries.filter(([, d]) => !d.isView);
|
|
4313
|
-
const makeAggregatedContent = (
|
|
5212
|
+
const makeAggregatedContent = (outputDir, items) => {
|
|
4314
5213
|
const imports = [];
|
|
4315
5214
|
const generatedClassNames = new Set(items.map(([name]) => name));
|
|
4316
5215
|
if (!basePageClassPath) {
|
|
@@ -4325,84 +5224,22 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
4325
5224
|
imports.push(`export * from "${runtimeClassGenRel}/playwright-types";`);
|
|
4326
5225
|
imports.push(`export * from "${runtimeClassGenRel}/Pointer";`);
|
|
4327
5226
|
imports.push(`export * from "${runtimeClassGenRel}/BasePage";`);
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
"
|
|
4340
|
-
|
|
4341
|
-
]);
|
|
4342
|
-
const usedImportIdentifiers = /* @__PURE__ */ new Set();
|
|
4343
|
-
const customPomClassIdentifierMap2 = {};
|
|
4344
|
-
const customPomMethodSignaturesByClass2 = /* @__PURE__ */ new Map();
|
|
4345
|
-
const ensureUniqueIdentifier = (base2) => {
|
|
4346
|
-
let candidate = base2;
|
|
4347
|
-
let i = 2;
|
|
4348
|
-
while (reservedIdentifiers.has(candidate) || usedImportIdentifiers.has(candidate)) {
|
|
4349
|
-
candidate = `${base2}${i}`;
|
|
4350
|
-
i++;
|
|
4351
|
-
}
|
|
4352
|
-
usedImportIdentifiers.add(candidate);
|
|
4353
|
-
return candidate;
|
|
4354
|
-
};
|
|
4355
|
-
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
4356
|
-
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
4357
|
-
if (!fs.existsSync(customDirAbs)) {
|
|
4358
|
-
return {
|
|
4359
|
-
classIdentifierMap: customPomClassIdentifierMap2,
|
|
4360
|
-
methodSignaturesByClass: customPomMethodSignaturesByClass2
|
|
4361
|
-
};
|
|
4362
|
-
}
|
|
4363
|
-
const files = fs.readdirSync(customDirAbs).filter((f) => f.endsWith(".ts")).sort((a, b) => a.localeCompare(b));
|
|
4364
|
-
for (const file of files) {
|
|
4365
|
-
const exportName = file.replace(/\.ts$/i, "");
|
|
4366
|
-
const requested = importAliases[exportName] ?? exportName;
|
|
4367
|
-
const collidesWithGeneratedClass = generatedClassNames.has(requested);
|
|
4368
|
-
const explicitAliasProvided = Object.prototype.hasOwnProperty.call(importAliases, exportName);
|
|
4369
|
-
if (collidesWithGeneratedClass && importCollisionBehavior === "error") {
|
|
4370
|
-
throw new Error(
|
|
4371
|
-
`[vue-pom-generator] Custom POM import name collision detected for "${exportName}".
|
|
4372
|
-
The identifier "${requested}" conflicts with a generated POM class.
|
|
4373
|
-
Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] to a unique name, or set generation.playwright.customPoms.importNameCollisionBehavior = "alias" to auto-alias collisions.`
|
|
4374
|
-
);
|
|
4375
|
-
}
|
|
4376
|
-
let localIdentifier = requested;
|
|
4377
|
-
if (collidesWithGeneratedClass && importCollisionBehavior === "alias") {
|
|
4378
|
-
const aliasBase = explicitAliasProvided ? requested : `${exportName}Custom`;
|
|
4379
|
-
localIdentifier = ensureUniqueIdentifier(aliasBase);
|
|
4380
|
-
} else {
|
|
4381
|
-
localIdentifier = ensureUniqueIdentifier(requested);
|
|
4382
|
-
}
|
|
4383
|
-
const customFileAbs = path.join(customDirAbs, file);
|
|
4384
|
-
customPomClassIdentifierMap2[exportName] = localIdentifier;
|
|
4385
|
-
const customPomMethodSignatures = extractCustomPomMethodSignatures(fs.readFileSync(customFileAbs, "utf8"), exportName);
|
|
4386
|
-
if (customPomMethodSignatures.size > 0) {
|
|
4387
|
-
customPomMethodSignaturesByClass2.set(exportName, customPomMethodSignatures);
|
|
4388
|
-
}
|
|
4389
|
-
const fromOutputDir = outputDir;
|
|
4390
|
-
const importPath = stripExtension(toPosixRelativePath(fromOutputDir, customFileAbs));
|
|
4391
|
-
if (localIdentifier !== exportName) {
|
|
4392
|
-
imports.push(`import { ${exportName} as ${localIdentifier} } from "${importPath}";`);
|
|
4393
|
-
} else {
|
|
4394
|
-
imports.push(`import { ${exportName} } from "${importPath}";`);
|
|
4395
|
-
}
|
|
5227
|
+
const customPomImportResolution = resolveCustomPomImportResolution(generatedClassNames, projectRoot, {
|
|
5228
|
+
customPomDir: options.customPomDir,
|
|
5229
|
+
customPomImportAliases: options.customPomImportAliases,
|
|
5230
|
+
customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior
|
|
5231
|
+
});
|
|
5232
|
+
const customPomClassIdentifierMap = customPomImportResolution.classIdentifierMap;
|
|
5233
|
+
const customPomMethodSignaturesByClass = customPomImportResolution.methodSignaturesByClass;
|
|
5234
|
+
const customPomAvailableClassIdentifiers = customPomImportResolution.availableClassIdentifiers;
|
|
5235
|
+
for (const importSpecifier of Object.values(customPomImportResolution.importSpecifiersByClass).sort((left, right) => left.exportName.localeCompare(right.exportName))) {
|
|
5236
|
+
const importPath = stripExtension(toPosixRelativePath(outputDir, importSpecifier.absolutePath));
|
|
5237
|
+
if (importSpecifier.localIdentifier !== importSpecifier.exportName) {
|
|
5238
|
+
imports.push(`import { ${importSpecifier.exportName} as ${importSpecifier.localIdentifier} } from "${importPath}";`);
|
|
5239
|
+
continue;
|
|
4396
5240
|
}
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
methodSignaturesByClass: customPomMethodSignaturesByClass2
|
|
4400
|
-
};
|
|
4401
|
-
};
|
|
4402
|
-
const customPomImportResolution = addCustomPomImports();
|
|
4403
|
-
const customPomClassIdentifierMap = customPomImportResolution?.classIdentifierMap ?? {};
|
|
4404
|
-
const customPomMethodSignaturesByClass = customPomImportResolution?.methodSignaturesByClass ?? /* @__PURE__ */ new Map();
|
|
4405
|
-
const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap));
|
|
5241
|
+
imports.push(`import { ${importSpecifier.exportName} } from "${importPath}";`);
|
|
5242
|
+
}
|
|
4406
5243
|
const referencedTargets = /* @__PURE__ */ new Set();
|
|
4407
5244
|
for (const [, deps] of items) {
|
|
4408
5245
|
for (const dt of deps.dataTestIdSet) {
|
|
@@ -4414,145 +5251,18 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
4414
5251
|
const stubTargets = Array.from(referencedTargets).filter((t) => !generatedClassNames.has(t)).sort((a, b) => a.localeCompare(b));
|
|
4415
5252
|
const availableClassNames = /* @__PURE__ */ new Set([...generatedClassNames, ...stubTargets]);
|
|
4416
5253
|
const depsByClassName = new Map(entries);
|
|
4417
|
-
const scanPascalCaseTags = (template) => {
|
|
4418
|
-
const names = [];
|
|
4419
|
-
const len = template.length;
|
|
4420
|
-
let i = 0;
|
|
4421
|
-
while (i < len) {
|
|
4422
|
-
const ch = template[i];
|
|
4423
|
-
if (ch !== "<") {
|
|
4424
|
-
i++;
|
|
4425
|
-
continue;
|
|
4426
|
-
}
|
|
4427
|
-
i++;
|
|
4428
|
-
if (i >= len)
|
|
4429
|
-
break;
|
|
4430
|
-
if (template[i] === "/" || template[i] === "!" || template[i] === "?") {
|
|
4431
|
-
i++;
|
|
4432
|
-
continue;
|
|
4433
|
-
}
|
|
4434
|
-
while (i < len && (template[i] === " " || template[i] === "\n" || template[i] === " " || template[i] === "\r")) i++;
|
|
4435
|
-
if (i >= len)
|
|
4436
|
-
break;
|
|
4437
|
-
const first = template[i];
|
|
4438
|
-
if (first < "A" || first > "Z") {
|
|
4439
|
-
continue;
|
|
4440
|
-
}
|
|
4441
|
-
const start = i;
|
|
4442
|
-
i++;
|
|
4443
|
-
while (i < len) {
|
|
4444
|
-
const c = template[i];
|
|
4445
|
-
const isLetter = c >= "A" && c <= "Z" || c >= "a" && c <= "z";
|
|
4446
|
-
const isDigit = c >= "0" && c <= "9";
|
|
4447
|
-
const isUnderscore = c === "_";
|
|
4448
|
-
if (isLetter || isDigit || isUnderscore) {
|
|
4449
|
-
i++;
|
|
4450
|
-
continue;
|
|
4451
|
-
}
|
|
4452
|
-
break;
|
|
4453
|
-
}
|
|
4454
|
-
const name = template.slice(start, i);
|
|
4455
|
-
if (name)
|
|
4456
|
-
names.push(name);
|
|
4457
|
-
}
|
|
4458
|
-
return Array.from(new Set(names));
|
|
4459
|
-
};
|
|
4460
|
-
const getComposedStubBody = (targetClassName) => {
|
|
4461
|
-
const mapped = vueFilesPathMap.get(targetClassName);
|
|
4462
|
-
const candidates = [
|
|
4463
|
-
mapped,
|
|
4464
|
-
path.join(projectRoot, "src", "views", `${targetClassName}.vue`),
|
|
4465
|
-
path.join(projectRoot, "src", "components", `${targetClassName}.vue`)
|
|
4466
|
-
].filter((p) => typeof p === "string" && p.length > 0);
|
|
4467
|
-
const filePath = candidates.find((p) => fs.existsSync(p));
|
|
4468
|
-
if (!filePath)
|
|
4469
|
-
return void 0;
|
|
4470
|
-
let source = "";
|
|
4471
|
-
try {
|
|
4472
|
-
source = fs.readFileSync(filePath, "utf8");
|
|
4473
|
-
} catch {
|
|
4474
|
-
return void 0;
|
|
4475
|
-
}
|
|
4476
|
-
const templateOpen = source.indexOf("<template");
|
|
4477
|
-
const templateClose = source.lastIndexOf("</template>");
|
|
4478
|
-
if (templateOpen === -1 || templateClose === -1 || templateClose <= templateOpen)
|
|
4479
|
-
return void 0;
|
|
4480
|
-
const afterOpenTag = source.indexOf(">", templateOpen);
|
|
4481
|
-
if (afterOpenTag === -1 || afterOpenTag >= templateClose)
|
|
4482
|
-
return void 0;
|
|
4483
|
-
const template = source.slice(afterOpenTag + 1, templateClose);
|
|
4484
|
-
if (!template)
|
|
4485
|
-
return void 0;
|
|
4486
|
-
const tags = scanPascalCaseTags(template);
|
|
4487
|
-
const childClassNames = Array.from(
|
|
4488
|
-
new Set(
|
|
4489
|
-
tags.filter((name) => availableClassNames.has(name)).filter((name) => name !== targetClassName)
|
|
4490
|
-
)
|
|
4491
|
-
).sort((a, b) => a.localeCompare(b));
|
|
4492
|
-
if (!childClassNames.length)
|
|
4493
|
-
return void 0;
|
|
4494
|
-
const methodToChildren = /* @__PURE__ */ new Map();
|
|
4495
|
-
for (const child of childClassNames) {
|
|
4496
|
-
const childDeps = depsByClassName.get(child);
|
|
4497
|
-
const methods = childDeps?.generatedMethods;
|
|
4498
|
-
if (!methods)
|
|
4499
|
-
continue;
|
|
4500
|
-
for (const [name, sig] of methods.entries()) {
|
|
4501
|
-
if (!sig)
|
|
4502
|
-
continue;
|
|
4503
|
-
const list = methodToChildren.get(name) ?? [];
|
|
4504
|
-
list.push({ child, params: sig.params, argNames: sig.argNames });
|
|
4505
|
-
methodToChildren.set(name, list);
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
|
-
const passthroughLines = [];
|
|
4509
|
-
for (const [methodName, candidatesForMethod] of methodToChildren.entries()) {
|
|
4510
|
-
if (candidatesForMethod.length !== 1)
|
|
4511
|
-
continue;
|
|
4512
|
-
if (methodName === "constructor")
|
|
4513
|
-
continue;
|
|
4514
|
-
const { child, params, argNames } = candidatesForMethod[0];
|
|
4515
|
-
const callArgs = argNames.join(", ");
|
|
4516
|
-
passthroughLines.push(
|
|
4517
|
-
"",
|
|
4518
|
-
` async ${methodName}(${params}) {`,
|
|
4519
|
-
` return await this.${child}.${methodName}(${callArgs});`,
|
|
4520
|
-
" }"
|
|
4521
|
-
);
|
|
4522
|
-
}
|
|
4523
|
-
return {
|
|
4524
|
-
childClassNames,
|
|
4525
|
-
lines: [
|
|
4526
|
-
...childClassNames.map((c) => ` ${c}: ${c};`),
|
|
4527
|
-
"",
|
|
4528
|
-
" constructor(page: PwPage) {",
|
|
4529
|
-
" super(page);",
|
|
4530
|
-
...childClassNames.map((c) => ` this.${c} = new ${c}(page);`),
|
|
4531
|
-
" }",
|
|
4532
|
-
...passthroughLines
|
|
4533
|
-
]
|
|
4534
|
-
};
|
|
4535
|
-
};
|
|
4536
5254
|
const stubs = stubTargets.map(
|
|
4537
5255
|
(t) => (() => {
|
|
4538
|
-
const composed = getComposedStubBody(t);
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
return [
|
|
4545
|
-
"/**\n * Stub POM generated because it is referenced as a navigation target but\n * did not have any generated test ids in this build.\n */",
|
|
4546
|
-
`export class ${t} extends BasePage {`,
|
|
4547
|
-
...body,
|
|
4548
|
-
"}"
|
|
4549
|
-
].join("\n");
|
|
5256
|
+
const composed = getComposedStubBody(t, availableClassNames, depsByClassName, vueFilesPathMap, projectRoot);
|
|
5257
|
+
return {
|
|
5258
|
+
className: t,
|
|
5259
|
+
members: composed?.members ?? getDefaultStubMembers(),
|
|
5260
|
+
isStub: true
|
|
5261
|
+
};
|
|
4550
5262
|
})()
|
|
4551
5263
|
);
|
|
4552
|
-
const classes = items.map(
|
|
4553
|
-
|
|
4554
|
-
outputDir,
|
|
4555
|
-
aggregated: true,
|
|
5264
|
+
const classes = items.map(([name, deps]) => {
|
|
5265
|
+
const prepared = prepareViewObjectModelClass(name, deps, componentHierarchyMap, {
|
|
4556
5266
|
customPomAttachments: options.customPomAttachments ?? [],
|
|
4557
5267
|
customPomClassIdentifierMap,
|
|
4558
5268
|
customPomAvailableClassIdentifiers,
|
|
@@ -4560,67 +5270,70 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
4560
5270
|
testIdAttribute: options.testIdAttribute,
|
|
4561
5271
|
vueRouterFluentChaining: options.vueRouterFluentChaining,
|
|
4562
5272
|
routeMetaByComponent: options.routeMetaByComponent
|
|
4563
|
-
})
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
5273
|
+
});
|
|
5274
|
+
const sourceRel = toPosixRelativePath(outputDir, deps.filePath);
|
|
5275
|
+
const kind = deps.isView ? "Page" : "Component";
|
|
5276
|
+
return {
|
|
5277
|
+
className: prepared.className,
|
|
5278
|
+
doc: `/** ${kind} POM: ${name} (source: ${sourceRel}) */`,
|
|
5279
|
+
members: prepared.members,
|
|
5280
|
+
isStub: false
|
|
5281
|
+
};
|
|
5282
|
+
});
|
|
5283
|
+
const prefixText = buildFilePrefix({
|
|
5284
|
+
referenceLib: "es2015",
|
|
5285
|
+
eslintDisableSortImports: true,
|
|
5286
|
+
commentLines: [
|
|
5287
|
+
"Aggregated generated POMs",
|
|
5288
|
+
"DO NOT MODIFY BY HAND",
|
|
5289
|
+
"",
|
|
5290
|
+
"This file is auto-generated by vue-pom-generator.",
|
|
5291
|
+
"Changes should be made in the generator/template, not in the generated output."
|
|
5292
|
+
]
|
|
5293
|
+
});
|
|
5294
|
+
return renderSourceFile("page-object-models.g.ts", (sourceFile) => {
|
|
5295
|
+
for (const line of imports) {
|
|
5296
|
+
sourceFile.addStatements(line);
|
|
5297
|
+
}
|
|
5298
|
+
for (const entry of [...classes, ...stubs]) {
|
|
5299
|
+
if (entry.isStub) {
|
|
5300
|
+
sourceFile.addStatements(buildCommentBlock([
|
|
5301
|
+
"Stub POM generated because it is referenced as a navigation target but",
|
|
5302
|
+
"did not have any generated test ids in this build."
|
|
5303
|
+
]).trimEnd());
|
|
5304
|
+
} else {
|
|
5305
|
+
sourceFile.addStatements(entry.doc);
|
|
5306
|
+
}
|
|
5307
|
+
const classDeclaration = sourceFile.addClass({
|
|
5308
|
+
name: entry.className,
|
|
5309
|
+
isExported: true,
|
|
5310
|
+
extends: "BasePage"
|
|
5311
|
+
});
|
|
5312
|
+
for (const member of entry.members) {
|
|
5313
|
+
addClassMember(classDeclaration, member);
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
}, { prefixText });
|
|
4572
5317
|
};
|
|
4573
5318
|
const base = ensureDir(outDir);
|
|
4574
5319
|
const outputFile = path.join(base, "page-object-models.g.ts");
|
|
4575
|
-
const
|
|
4576
|
-
${eslintSuppressionHeader}/**
|
|
4577
|
-
* Aggregated generated POMs
|
|
4578
|
-
${AUTO_GENERATED_COMMENT}`;
|
|
4579
|
-
const content = makeAggregatedContent(header, path.dirname(outputFile), [...views, ...components]);
|
|
5320
|
+
const content = makeAggregatedContent(path.dirname(outputFile), [...views, ...components]);
|
|
4580
5321
|
const indexFile = path.join(base, "index.ts");
|
|
4581
|
-
const indexContent =
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
const resolvePluginAsset = (relative) => {
|
|
4597
|
-
try {
|
|
4598
|
-
return fileURLToPath(new URL(relative, import.meta.url));
|
|
4599
|
-
} catch {
|
|
4600
|
-
return path.resolve(__dirname, relative);
|
|
4601
|
-
}
|
|
4602
|
-
};
|
|
4603
|
-
const clickInstrumentationAbs = resolvePluginAsset("../click-instrumentation.ts");
|
|
4604
|
-
const pointerAbs = resolvePluginAsset("../class-generation/Pointer.ts");
|
|
4605
|
-
const playwrightTypesAbs = resolvePluginAsset("../class-generation/playwright-types.ts");
|
|
4606
|
-
const runtimeFiles = [
|
|
4607
|
-
{
|
|
4608
|
-
filePath: path.join(runtimeDirAbs, "click-instrumentation.ts"),
|
|
4609
|
-
content: readText(clickInstrumentationAbs, "click-instrumentation.ts")
|
|
4610
|
-
},
|
|
4611
|
-
{
|
|
4612
|
-
filePath: path.join(runtimeClassGenAbs, "Pointer.ts"),
|
|
4613
|
-
content: readText(pointerAbs, "Pointer.ts")
|
|
4614
|
-
},
|
|
4615
|
-
{
|
|
4616
|
-
filePath: path.join(runtimeClassGenAbs, "playwright-types.ts"),
|
|
4617
|
-
content: readText(playwrightTypesAbs, "playwright-types.ts")
|
|
4618
|
-
},
|
|
4619
|
-
{
|
|
4620
|
-
filePath: path.join(runtimeClassGenAbs, "BasePage.ts"),
|
|
4621
|
-
content: readText(basePageClassPath, "BasePage.ts")
|
|
4622
|
-
}
|
|
4623
|
-
];
|
|
5322
|
+
const indexContent = renderSourceFile("index.ts", (sourceFile) => {
|
|
5323
|
+
addExportAll(sourceFile, "./page-object-models.g");
|
|
5324
|
+
}, {
|
|
5325
|
+
prefixText: buildFilePrefix({
|
|
5326
|
+
eslintDisableSortImports: true,
|
|
5327
|
+
commentLines: [
|
|
5328
|
+
"POM exports",
|
|
5329
|
+
"DO NOT MODIFY BY HAND",
|
|
5330
|
+
"",
|
|
5331
|
+
"This file is auto-generated by vue-pom-generator.",
|
|
5332
|
+
"Changes should be made in the generator/template, not in the generated output."
|
|
5333
|
+
]
|
|
5334
|
+
})
|
|
5335
|
+
});
|
|
5336
|
+
const runtimeFiles = buildRuntimeGeneratedFiles(base, basePageClassPath);
|
|
4624
5337
|
return [
|
|
4625
5338
|
{ filePath: outputFile, content },
|
|
4626
5339
|
{ filePath: indexFile, content: indexContent },
|
|
@@ -4710,48 +5423,50 @@ function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassI
|
|
|
4710
5423
|
return out;
|
|
4711
5424
|
}
|
|
4712
5425
|
function getComponentInstances(childrenComponent, componentHierarchyMap, attachmentsForThisView = [], widgetInstances = []) {
|
|
4713
|
-
|
|
5426
|
+
const declarations = [];
|
|
4714
5427
|
for (const a of attachmentsForThisView) {
|
|
4715
|
-
|
|
4716
|
-
|
|
5428
|
+
declarations.push(createClassProperty({
|
|
5429
|
+
name: a.propertyName,
|
|
5430
|
+
type: a.className
|
|
5431
|
+
}));
|
|
4717
5432
|
}
|
|
4718
5433
|
for (const w of widgetInstances) {
|
|
4719
|
-
|
|
4720
|
-
|
|
5434
|
+
declarations.push(createClassProperty({
|
|
5435
|
+
name: w.propertyName,
|
|
5436
|
+
type: w.className
|
|
5437
|
+
}));
|
|
4721
5438
|
}
|
|
4722
5439
|
childrenComponent.forEach((child) => {
|
|
4723
5440
|
if (componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size) {
|
|
4724
5441
|
const childName = child.split(".vue")[0];
|
|
4725
|
-
|
|
4726
|
-
|
|
5442
|
+
declarations.push(createClassProperty({
|
|
5443
|
+
name: childName,
|
|
5444
|
+
type: childName
|
|
5445
|
+
}));
|
|
4727
5446
|
}
|
|
4728
5447
|
});
|
|
4729
|
-
return
|
|
4730
|
-
`;
|
|
5448
|
+
return declarations;
|
|
4731
5449
|
}
|
|
4732
5450
|
function getConstructor(childrenComponent, componentHierarchyMap, attachmentsForThisView = [], widgetInstances = [], options) {
|
|
4733
|
-
let content = " constructor(page: PwPage) {\n";
|
|
4734
5451
|
const attr = (options?.testIdAttribute ?? "data-testid").trim() || "data-testid";
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
5452
|
+
return createClassConstructor({
|
|
5453
|
+
parameters: [{ name: "page", type: "PwPage" }],
|
|
5454
|
+
statements: (writer) => {
|
|
5455
|
+
writer.writeLine(`super(page, { testIdAttribute: ${JSON.stringify(attr)} });`);
|
|
5456
|
+
for (const a of attachmentsForThisView) {
|
|
5457
|
+
writer.writeLine(`this.${a.propertyName} = new ${a.className}(page, this);`);
|
|
5458
|
+
}
|
|
5459
|
+
for (const w of widgetInstances) {
|
|
5460
|
+
writer.writeLine(`this.${w.propertyName} = new ${w.className}(page, ${JSON.stringify(w.testId)});`);
|
|
5461
|
+
}
|
|
5462
|
+
childrenComponent.forEach((child) => {
|
|
5463
|
+
if (componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size) {
|
|
5464
|
+
const childName = child.split(".vue")[0];
|
|
5465
|
+
writer.writeLine(`this.${childName} = new ${childName}(page);`);
|
|
5466
|
+
}
|
|
5467
|
+
});
|
|
4750
5468
|
}
|
|
4751
5469
|
});
|
|
4752
|
-
content += " }";
|
|
4753
|
-
return `${content}
|
|
4754
|
-
`;
|
|
4755
5470
|
}
|
|
4756
5471
|
const TESTID_CLICK_EVENT_NAME = "__testid_event__";
|
|
4757
5472
|
const TESTID_CLICK_EVENT_STRICT_FLAG = "__testid_click_event_strict__";
|
|
@@ -5886,6 +6601,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
5886
6601
|
normalizedBasePagePath,
|
|
5887
6602
|
outDir,
|
|
5888
6603
|
emitLanguages,
|
|
6604
|
+
typescriptOutputStructure,
|
|
5889
6605
|
csharp,
|
|
5890
6606
|
generateFixtures,
|
|
5891
6607
|
customPomAttachments,
|
|
@@ -6090,6 +6806,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
6090
6806
|
await generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
|
|
6091
6807
|
outDir,
|
|
6092
6808
|
emitLanguages,
|
|
6809
|
+
typescriptOutputStructure,
|
|
6093
6810
|
csharp,
|
|
6094
6811
|
generateFixtures,
|
|
6095
6812
|
customPomAttachments,
|
|
@@ -6124,6 +6841,7 @@ function createDevProcessorPlugin(options) {
|
|
|
6124
6841
|
basePageClassPath,
|
|
6125
6842
|
outDir,
|
|
6126
6843
|
emitLanguages,
|
|
6844
|
+
typescriptOutputStructure,
|
|
6127
6845
|
csharp,
|
|
6128
6846
|
generateFixtures,
|
|
6129
6847
|
customPomAttachments,
|
|
@@ -6337,6 +7055,7 @@ function createDevProcessorPlugin(options) {
|
|
|
6337
7055
|
generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
|
|
6338
7056
|
outDir,
|
|
6339
7057
|
emitLanguages,
|
|
7058
|
+
typescriptOutputStructure,
|
|
6340
7059
|
csharp,
|
|
6341
7060
|
generateFixtures,
|
|
6342
7061
|
customPomAttachments,
|
|
@@ -6510,14 +7229,37 @@ function createDevProcessorPlugin(options) {
|
|
|
6510
7229
|
};
|
|
6511
7230
|
}
|
|
6512
7231
|
function generateTestIdsModule(componentTestIds) {
|
|
6513
|
-
const manifestEntries = Array.from(componentTestIds.entries()).sort((a, b) => a[0].localeCompare(b[0]))
|
|
6514
|
-
return
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
7232
|
+
const manifestEntries = Array.from(componentTestIds.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
7233
|
+
return renderSourceFile("virtual-testids.ts", (sourceFile) => {
|
|
7234
|
+
sourceFile.addStatements("// Virtual module: test id manifest");
|
|
7235
|
+
sourceFile.addVariableStatement({
|
|
7236
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
7237
|
+
isExported: true,
|
|
7238
|
+
declarations: [{
|
|
7239
|
+
name: "testIdManifest",
|
|
7240
|
+
initializer: (writer) => {
|
|
7241
|
+
writer.write("{").newLine();
|
|
7242
|
+
writer.indent(() => {
|
|
7243
|
+
manifestEntries.forEach(([componentName, testIds], index) => {
|
|
7244
|
+
const suffix = index === manifestEntries.length - 1 ? "" : ",";
|
|
7245
|
+
writer.writeLine(`${JSON.stringify(componentName)}: ${JSON.stringify(Array.from(testIds).sort())}${suffix}`);
|
|
7246
|
+
});
|
|
7247
|
+
});
|
|
7248
|
+
writer.write("} as const");
|
|
7249
|
+
}
|
|
7250
|
+
}]
|
|
7251
|
+
});
|
|
7252
|
+
sourceFile.addTypeAlias({
|
|
7253
|
+
isExported: true,
|
|
7254
|
+
name: "TestIdManifest",
|
|
7255
|
+
type: "typeof testIdManifest"
|
|
7256
|
+
});
|
|
7257
|
+
sourceFile.addTypeAlias({
|
|
7258
|
+
isExported: true,
|
|
7259
|
+
name: "ComponentName",
|
|
7260
|
+
type: "keyof TestIdManifest"
|
|
7261
|
+
});
|
|
7262
|
+
});
|
|
6521
7263
|
}
|
|
6522
7264
|
function createTestIdsVirtualModulesPlugin(componentTestIds) {
|
|
6523
7265
|
const maybeModule = virtualImport;
|
|
@@ -6541,6 +7283,7 @@ function createSupportPlugins(options) {
|
|
|
6541
7283
|
existingIdBehavior,
|
|
6542
7284
|
outDir,
|
|
6543
7285
|
emitLanguages,
|
|
7286
|
+
typescriptOutputStructure,
|
|
6544
7287
|
csharp,
|
|
6545
7288
|
routerAwarePoms,
|
|
6546
7289
|
routerEntry,
|
|
@@ -6584,6 +7327,7 @@ function createSupportPlugins(options) {
|
|
|
6584
7327
|
normalizedBasePagePath,
|
|
6585
7328
|
outDir,
|
|
6586
7329
|
emitLanguages,
|
|
7330
|
+
typescriptOutputStructure,
|
|
6587
7331
|
csharp,
|
|
6588
7332
|
generateFixtures,
|
|
6589
7333
|
customPomAttachments,
|
|
@@ -6615,6 +7359,7 @@ function createSupportPlugins(options) {
|
|
|
6615
7359
|
basePageClassPath,
|
|
6616
7360
|
outDir,
|
|
6617
7361
|
emitLanguages,
|
|
7362
|
+
typescriptOutputStructure,
|
|
6618
7363
|
csharp,
|
|
6619
7364
|
generateFixtures,
|
|
6620
7365
|
customPomAttachments,
|
|
@@ -7166,6 +7911,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7166
7911
|
const csharp = generationOptions?.csharp;
|
|
7167
7912
|
const errorBehavior = options.errorBehavior;
|
|
7168
7913
|
const missingSemanticNameBehavior = resolveMissingSemanticNameBehavior(errorBehavior);
|
|
7914
|
+
const typescriptOutputStructure = generationOptions?.playwright?.outputStructure ?? "aggregated";
|
|
7169
7915
|
const generateFixtures = generationOptions?.playwright?.fixtures;
|
|
7170
7916
|
const customPoms = generationOptions?.playwright?.customPoms;
|
|
7171
7917
|
const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
|
|
@@ -7200,6 +7946,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7200
7946
|
assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
|
|
7201
7947
|
if (generationEnabled) {
|
|
7202
7948
|
assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
|
|
7949
|
+
assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
|
|
7203
7950
|
assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
7204
7951
|
if (generationOptions?.router && routerType === "vue-router") {
|
|
7205
7952
|
assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
|
|
@@ -7247,6 +7994,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7247
7994
|
existingIdBehavior,
|
|
7248
7995
|
outDir,
|
|
7249
7996
|
emitLanguages,
|
|
7997
|
+
typescriptOutputStructure,
|
|
7250
7998
|
csharp,
|
|
7251
7999
|
routerAwarePoms,
|
|
7252
8000
|
routerEntry,
|