Skip to main content
Glama
jprealini

MCP Cypress Page Object Generator

Create Page Object File

create_Page_Object_file

Generate TypeScript Page Object classes for Cypress testing by analyzing web page URLs to automate test creation and element interaction workflows.

Instructions

Create Page Object file directly in the Cypress project by analyzing the provided URL

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesURL of the web page
workspacePathNoWorkspace path (optional, it is detected automatically if not provided)
pageObjectNameNoCustom name for the page object (optional)

Implementation Reference

  • src/index.js:359-393 (registration)
    Registration of the MCP tool 'create_Page_Object_file' including schema and inline handler function.
    server.registerTool(
        'create_Page_Object_file',
        {
            title: 'Create Page Object File',
            description: 'Create Page Object file directly in the Cypress project by analyzing the provided URL',
            inputSchema: {
                url: z.string().describe('URL of the web page'),
                workspacePath: z.string().optional().describe('Workspace path (optional, it is detected automatically if not provided)'),
                pageObjectName: z.string().optional().describe('Custom name for the page object (optional)')
            }
        },
        async ({ url, workspacePath, pageObjectName }) => {
            const fileManager = new CypressFileManager()
            try {
                const workspaceRoot = await fileManager.detectWorkspace(workspacePath)
                await fileManager.ensureDirectoryStructure(workspaceRoot)
                const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] })
                const page = await browser.newPage()
                await page.goto(url, { waitUntil: 'networkidle2' })
                const html = await page.content()
                await browser.close()
                const $ = cheerio.load(html)
                const pageObjectMeta = generatePageObjectClass($, url, pageObjectName)
                const pageObjectPath = await fileManager.createPageObject(workspaceRoot, url, pageObjectMeta)
                const indexPath = await fileManager.createIndexFile(workspaceRoot)
                return {
                    content: [
                        { type: 'text', text: `āœ… Files created successfully:\n\nšŸ“„ Page Object: ${pageObjectPath}\nšŸ“‹ Index File: ${indexPath}\n\nWorkspace detected: ${workspaceRoot}\n\nNow you can import the page object in your tests using:\nimport { ${pageObjectMeta.className} } from '../pages/${pageObjectMeta.featureName}'` }
                    ]
                }
            } catch (error) {
                return { content: [ { type: 'text', text: `āŒ Error creating Cypress files: ${error instanceof Error ? error.message : 'Unknown error'}\n\nMake sure you are in a directory with a valid Cypress project.` } ] }
            }
        }
    )
  • The core handler logic that fetches the webpage using Puppeteer, parses HTML with Cheerio, generates Page Object code, and writes files to the Cypress project directory.
    async ({ url, workspacePath, pageObjectName }) => {
        const fileManager = new CypressFileManager()
        try {
            const workspaceRoot = await fileManager.detectWorkspace(workspacePath)
            await fileManager.ensureDirectoryStructure(workspaceRoot)
            const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] })
            const page = await browser.newPage()
            await page.goto(url, { waitUntil: 'networkidle2' })
            const html = await page.content()
            await browser.close()
            const $ = cheerio.load(html)
            const pageObjectMeta = generatePageObjectClass($, url, pageObjectName)
            const pageObjectPath = await fileManager.createPageObject(workspaceRoot, url, pageObjectMeta)
            const indexPath = await fileManager.createIndexFile(workspaceRoot)
            return {
                content: [
                    { type: 'text', text: `āœ… Files created successfully:\n\nšŸ“„ Page Object: ${pageObjectPath}\nšŸ“‹ Index File: ${indexPath}\n\nWorkspace detected: ${workspaceRoot}\n\nNow you can import the page object in your tests using:\nimport { ${pageObjectMeta.className} } from '../pages/${pageObjectMeta.featureName}'` }
                ]
            }
        } catch (error) {
            return { content: [ { type: 'text', text: `āŒ Error creating Cypress files: ${error instanceof Error ? error.message : 'Unknown error'}\n\nMake sure you are in a directory with a valid Cypress project.` } ] }
        }
    }
  • Zod input schema defining parameters: url (required), workspacePath and pageObjectName (optional).
    inputSchema: {
        url: z.string().describe('URL of the web page'),
        workspacePath: z.string().optional().describe('Workspace path (optional, it is detected automatically if not provided)'),
        pageObjectName: z.string().optional().describe('Custom name for the page object (optional)')
    }
  • Key helper function that generates the Cypress Page Object class code by analyzing DOM elements (buttons, inputs, links, selects, textareas) and creating appropriate locators, getters, and interaction methods.
    function generatePageObjectClass($, url, customFeatureName = null) {
        const featureName = customFeatureName || getFeatureName($, url)
        const className = featureName.charAt(0).toUpperCase() + featureName.slice(1) + 'Page'
        const elements = []
        const getters = []
        const valueGetters = []
        const interactionMethods = []
        let elementCounter = 1
        const elementMeta = []
    
        // BUTTONS
            $('button').each((i, element) => {
                const $el = $(element);
                const { locator, rawName } = getLocatorAndRawName($el, 'button', { index: i });
                const elementName = toCamelCase(rawName);
                elements.push(`    ${elementName}: () => ${locator}`);
                interactionMethods.push(`    click${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().click() }`);
                valueGetters.push(`    getText${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().invoke('text') }`);
                elementCounter++;
            });
        // INPUTS
            $('input').each((i, element) => {
                const $el = $(element);
                const type = $el.attr('type') || 'text';
                const { locator, rawName } = getLocatorAndRawName($el, 'input', { type, index: i });
                const elementName = toCamelCase(rawName);
                elements.push(`    ${elementName}: () => ${locator}`);
                getters.push(`    get ${elementName}() { return this.#elements.${elementName}() }`);
                if (type === 'checkbox' || type === 'radio') {
                    interactionMethods.push(`    check${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().check() }`);
                    interactionMethods.push(`    uncheck${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().uncheck() }`);
                    valueGetters.push(`    isChecked${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().should('have.prop', 'checked') }`);
                } else if (type === 'submit' || type === 'button') {
                    interactionMethods.push(`    click${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().click() }`);
                    valueGetters.push(`    getText${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().invoke('text') }`);
                } else {
                    interactionMethods.push(`    type${elementName.charAt(0).toUpperCase() + elementName.slice(1)}(text) { return this.#elements.${elementName}().type(text) }`);
                    interactionMethods.push(`    clear${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().clear() }`);
                    valueGetters.push(`    getValue${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().invoke('val') }`);
                }
                elementCounter++;
            });
        // LINKS
            $('a').each((i, element) => {
                const $el = $(element);
                const { locator, rawName } = getLocatorAndRawName($el, 'a', { index: i });
                const elementName = toCamelCase(rawName);
                elements.push(`    ${elementName}: () => ${locator}`);
                getters.push(`    get ${elementName}() { return this.#elements.${elementName}() }`);
                interactionMethods.push(`    click${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().click() }`);
                valueGetters.push(`    getText${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().invoke('text') }`);
                elementCounter++;
            });
        // SELECTS
            $('select').each((i, element) => {
                const $el = $(element);
                const { locator, rawName } = getLocatorAndRawName($el, 'select', { index: i });
                const elementName = toCamelCase(rawName);
                elements.push(`    ${elementName}: () => ${locator}`);
                getters.push(`    get ${elementName}() { return this.#elements.${elementName}() }`);
                interactionMethods.push(`    select${elementName.charAt(0).toUpperCase() + elementName.slice(1)}(value) { return this.#elements.${elementName}().select(value) }`);
                valueGetters.push(`    getValue${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().invoke('val') }`);
                elementCounter++;
            });
        // TEXTAREAS
            $('textarea').each((i, element) => {
                const $el = $(element);
                const { locator, rawName } = getLocatorAndRawName($el, 'textarea', { index: i });
                const elementName = toCamelCase(rawName);
                elements.push(`    ${elementName}: () => ${locator}`);
                getters.push(`    get ${elementName}() { return this.#elements.${elementName}() }`);
                interactionMethods.push(`    type${elementName.charAt(0).toUpperCase() + elementName.slice(1)}(text) { return this.#elements.${elementName}().type(text) }`);
                interactionMethods.push(`    clear${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().clear() }`);
                valueGetters.push(`    getValue${elementName.charAt(0).toUpperCase() + elementName.slice(1)}() { return this.#elements.${elementName}().invoke('val') }`);
                elementCounter++;
            });    
        return {
            classCode: `export class ${className} {\n  // Private elements\n  #elements = {\n${elements.join(',\n')}\n  }\n\n  // Element meta (currently not used for bulk actions)\n\n  // Public getters\n${getters.join('\n')}\n\n  // Value/State getters\n${valueGetters.join('\n')}\n\n  // Interaction methods (per-element actions)\n${interactionMethods.join('\n')}\n}\n`,
            className,
            featureName
        }
    }
  • Helper method in CypressFileManager class that writes the generated Page Object code to the file system in cypress/pages directory, with backup of existing files.
    async createPageObject(workspaceRoot, url, pageObjectMeta) {
        const { featureName, classCode } = pageObjectMeta
        const fileName = `${featureName}.js`
        const filePath = path.join(workspaceRoot, 'cypress', 'pages', fileName)
        
        if (await this.fileExists(filePath)) {
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
            const backupPath = path.join(workspaceRoot, 'cypress', 'pages', `${featureName}.backup.${timestamp}.js`)
            await fs.copyFile(filePath, backupPath)
        }
        
        await fs.writeFile(filePath, classCode, 'utf8')
        return filePath
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool creates a file by analyzing a URL but omits critical details like what permissions are needed, how the analysis works, whether it overwrites existing files, error handling, or output format. This is inadequate for a tool that performs file creation and web analysis.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that directly states the tool's action and key input (URL). It is front-loaded with the core purpose and wastes no words, making it highly concise and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (involving file creation, URL analysis, and Cypress integration), no annotations, and no output schema, the description is insufficient. It lacks details on behavioral traits, error cases, or what the created file contains, leaving significant gaps for an AI agent to understand and use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all parameters. The description adds no additional meaning beyond implying URL analysis, which is already suggested by the parameter descriptions. This meets the baseline of 3 since the schema handles parameter documentation effectively.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Create Page Object file directly in the Cypress project by analyzing the provided URL.' It specifies the verb ('Create'), resource ('Page Object file'), and context ('Cypress project'), but lacks differentiation from siblings since none exist, making a perfect 5 unnecessary.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives, prerequisites, or exclusions. It mentions analyzing a URL but doesn't specify what types of URLs are suitable or any constraints, leaving usage unclear beyond the basic action.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jprealini/cypress-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server