You can use nx-plugins to generate a file based on another file.
For example, if you want to take all the id’s or attributes from an HTML document (of component) to set aside in a selector file.
Let’s create the generator using NX to generate the needed files.
nx generate @nx/plugin:generator selector-generator
schema.json
{ "$schema": "http://json-schema.org/schema", "$id": "selector", "title": "Create selector file based on HTML file", "type": "object", "properties": { "htmlFilePath": { "type": "string", "description": "The full path to the HTML file", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What is the full path to the HTML file (usually your component.ts or component.html)?" }, "outputDirectory": { "type": "string", "description": "The directory where the selector file will be created", "$default": { "$source": "argv", "index": 1 }, "x-prompt": "In which directory should the selector file be created?" } }, "required": ["htmlFilePath"] }
schema.d.ts
export interface SelectorGeneratorGeneratorSchema { htmlFilePath: string; outputDirectory?: string; }
generator.ts (note that you have to change the attributeName or you’ll get an empty file)
import type { Tree } from '@nx/devkit'; import { getWorkspaceLayout } from '@nx/devkit'; import * as path from 'path'; import { join } from 'path'; import type { SelectorGeneratorGeneratorSchema } from './schema'; /** * Generates a selectorFileContent based on another file * @param tree The Nx Tree (added by default) * @param schema The generator options * @example pnpm nx g @yourProject/nx-plugin:selector-generator or yarn nx g @yourProject/nx-plugin:selector-generator */ export default async function generateSelectorFile(tree: Tree, schema: SelectorGeneratorGeneratorSchema): Promise<void> { if (!tree.exists(schema.htmlFilePath)) { throw new Error(`File '${schema.htmlFilePath}' does not exist.`); } const content = tree.read(schema.htmlFilePath, 'utf-8') as string; const selectorValue = extractHostSgQaIdValue(content); const attributeValues = extractAttributeValues(content); const attributeContent = createSelectors(selectorValue, attributeValues); const selectorFileContent = addImports(attributeContent) + attributeContent; writeSelectorFileToDisk (tree, schema, selectorFileContent); } const extractHostSgQaIdValue = (content: string): string | null => { const qaIdRegex = /'qa-id':\s*'([^']+)'/; const match = content.match(sgQaIdRegex); return match ? match[1] : null; }; const extractAttributeValues = (content: string, attributeName = 'data-cy'): string[] => { const attributeRegex = new RegExp(`${attributeName}="([^"]+)"`, 'g'); const matches = content.match(attributeRegex); if (matches) { return matches.map((match) => match.slice(10, -1)); } return []; }; const hyphenToPascal = (input: string): string => input.replace(/(?:^|-)([a-z])/g, (_match, group1) => group1.toUpperCase()); const createSelectors = (componentSelector: string | null, selectors: string[]): string => { let selectorFileContent = ''; selectors.forEach((selector) => { const variableName = `get${hyphenToPascal(selector)}`; selectorFileContent += `export const ${variableName} = cy.get('[data-cy="${selector}"]'); \n`; }); return selectorFileContent; }; /** * Creates a new file with the specified content */ const writeSelectorFileToDisk = (tree: Tree, schema: SelectorGeneratorGeneratorSchema, selectorFileContent: string): void => { const fileName = path .basename(schema.htmlFilePath) .replace(/([a-z])([A-Z])/g, '$1-$2') .toLowerCase(); const outputDir = schema.outputDirectory ?? getWorkspaceLayout(tree).libsDir; const outputFilePath = join(`${outputDir}`, `${fileName}.selectors.ts`).replace('.component.ts', ''); tree.write(outputFilePath, selectorFileContent); };
generator.spec
import type { Tree } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { join } from 'path'; import generateSelectorFile from './generator'; import type { SelectorGeneratorGeneratorSchema } from './schema'; describe('Selector file generator', () => { let libTree: Tree; beforeEach(() => { libTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); libTree.write( 'libs/abc/test/feature-manage/src/lib/test-form/test-form.component.ts', ` template: \` <form [formGroup]="form"> <mat-form-field data-cy="some-form-field"> <input data-cy="some-input" /> </mat-form-field> </form> }) \` export class TestFormComponent { }` ); }); it('should generate the selector file', async () => { const options = { htmlFilePath: 'libs/abc/test/feature-manage/src/lib/test-form/test-form.component.ts', outputDirectory: 'apps/abc/app-e2e/src/support/test', } satisfies SelectorGeneratorGeneratorSchema; await generateSelectorFile(libTree, options); // Read the generated file's content const fileContents = libTree.read(join(options.outputDirectory, 'test-form.selectors.ts'), 'utf-8'); // Check if the file is generated and has content expect(fileContents).toContain(`export const getTestFormComponent = cy.get('[data-cy="some-form-field"]');`); expect(fileContents).toContain(`export const getTestFormComponent = cy.get('[data-cy="some-input"]');`); }); });
You can now run
pnpm nx g @yourProject/nx-plugin:selector-generator or yarn nx g @yourProject/nx-plugin:selector-generator