Skip to main content
Glama
generate-pom.js23.9 kB
"use strict"; /** * Generate POM Tool - Auto-generate Page Object Model classes * * @author Naveen AutomationLabs * @license MIT * @date 2025 * @see https://github.com/naveenanimation20/locatorlabs-mcp */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeneratePOMTool = void 0; const MAX_ELEMENTS = 30; const MAX_TEXT_LENGTH = 50; class GeneratePOMTool { browserManager; constructor(browserManager) { this.browserManager = browserManager; } async execute(url, className, language = "typescript") { const page = await this.browserManager.navigateTo(url); const elements = await this.getPageElements(page); // Generate properties for each element const properties = elements.map((el) => ({ propertyName: this.generatePropertyName(el), locator: this.getBestLocator(el), elementType: el.tagName, description: this.describeElement(el), // Add Selenium-specific locators seleniumLocator: this.getSeleniumLocator(el), id: el.id, name: el.name, xpath: this.getXPath(el), cssSelector: this.getCssSelector(el), })); // Remove duplicates by property name const uniqueProperties = this.deduplicateProperties(properties); // Generate code based on language let code; switch (language) { case "python": code = this.generatePythonPOM(className, uniqueProperties, url); break; case "javascript": code = this.generateJavaScriptPOM(className, uniqueProperties, url); break; case "java-selenium": code = this.generateJavaSeleniumPOM(className, uniqueProperties, url); break; case "python-selenium": code = this.generatePythonSeleniumPOM(className, uniqueProperties, url); break; case "csharp-selenium": code = this.generateCSharpSeleniumPOM(className, uniqueProperties, url); break; default: code = this.generateTypeScriptPOM(className, uniqueProperties, url); } return { url, className, language, elementsFound: uniqueProperties.length, code, }; } async getPageElements(page) { const elements = await page.evaluate((maxText) => { const selectors = [ "button", "a[href]", "input", "select", "textarea", "[role='button']", "[role='link']", "[role='textbox']", "[role='checkbox']", "[role='radio']", ]; const results = []; const seen = new Set(); selectors.forEach((sel) => { document.querySelectorAll(sel).forEach((el) => { if (seen.has(el)) return; seen.add(el); const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); if (rect.width === 0 || rect.height === 0 || style.display === "none" || style.visibility === "hidden") { return; } const htmlEl = el; const inputEl = el; results.push({ tagName: el.tagName.toLowerCase(), id: el.id || undefined, name: inputEl.name || undefined, className: el.className?.toString() || undefined, type: inputEl.type || undefined, placeholder: inputEl.placeholder || undefined, text: htmlEl.innerText?.trim().substring(0, maxText) || undefined, ariaLabel: el.getAttribute("aria-label") || undefined, role: el.getAttribute("role") || undefined, testId: el.getAttribute("data-testid") || el.getAttribute("data-test-id") || el.getAttribute("data-cy") || undefined, }); }); }); return results; }, MAX_TEXT_LENGTH); return elements.slice(0, MAX_ELEMENTS); } generatePropertyName(el) { // Priority: testId > id > name > ariaLabel > text > placeholder > generic let baseName = el.testId || el.id || el.name || el.ariaLabel || el.text || el.placeholder || el.tagName; // Clean the name baseName = baseName .replace(/[^a-zA-Z0-9\s]/g, " ") .trim() .replace(/\s+/g, " "); // Convert to camelCase const words = baseName.split(" ").slice(0, 4); const camelCase = words .map((word, index) => { const lower = word.toLowerCase(); return index === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1); }) .join(""); // Add suffix based on element type const suffix = this.getElementSuffix(el); const name = camelCase + suffix; return name || "element"; } getElementSuffix(el) { const type = el.type?.toLowerCase(); const tag = el.tagName.toLowerCase(); if (tag === "button" || type === "submit" || type === "button") return "Button"; if (tag === "a") return "Link"; if (tag === "select") return "Select"; if (tag === "textarea") return "Textarea"; if (type === "checkbox") return "Checkbox"; if (type === "radio") return "Radio"; if (type === "password") return "Input"; if (tag === "input") return "Input"; return ""; } getBestLocator(el) { // Priority order for Playwright best practices if (el.testId) { return `getByTestId('${this.escape(el.testId)}')`; } const role = el.role || this.inferRole(el); if (role && (el.ariaLabel || el.text)) { const name = el.ariaLabel || el.text; return `getByRole('${role}', { name: '${this.escape(name)}' })`; } if (el.ariaLabel) { return `getByLabel('${this.escape(el.ariaLabel)}')`; } if (el.placeholder) { return `getByPlaceholder('${this.escape(el.placeholder)}')`; } if (el.text && !["input", "textarea"].includes(el.tagName)) { return `getByText('${this.escape(el.text)}')`; } if (el.id) { return `locator('#${el.id}')`; } if (el.name) { return `locator('[name="${el.name}"]')`; } return `locator('${el.tagName}')`; } inferRole(el) { const tag = el.tagName.toLowerCase(); const type = el.type?.toLowerCase(); if (tag === "button" || type === "submit" || type === "button") return "button"; if (tag === "a") return "link"; if (tag === "select") return "combobox"; if (type === "checkbox") return "checkbox"; if (type === "radio") return "radio"; if (tag === "input" || tag === "textarea") return "textbox"; return null; } describeElement(el) { const parts = []; if (el.text) parts.push(`"${el.text}"`); if (el.placeholder) parts.push(`placeholder: ${el.placeholder}`); if (el.type) parts.push(`type: ${el.type}`); return parts.join(", ") || el.tagName; } deduplicateProperties(properties) { const seen = new Map(); return properties.map((prop) => { const count = seen.get(prop.propertyName) || 0; seen.set(prop.propertyName, count + 1); if (count > 0) { return { ...prop, propertyName: `${prop.propertyName}${count + 1}` }; } return prop; }); } escape(str) { return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").substring(0, MAX_TEXT_LENGTH); } generateTypeScriptPOM(className, properties, url) { const propDeclarations = properties .map((p) => ` readonly ${p.propertyName}: Locator; // ${p.description}`) .join("\n"); const propInitializations = properties .map((p) => ` this.${p.propertyName} = page.${p.locator};`) .join("\n"); const actions = this.generateTypeScriptActions(properties); return `import { Page, Locator, expect } from '@playwright/test'; /** * Page Object Model for: ${url} * Generated by LocatorLabs MCP * Elements: ${properties.length} */ export class ${className} { readonly page: Page; ${propDeclarations} constructor(page: Page) { this.page = page; ${propInitializations} } /** * Navigate to this page */ async navigate(): Promise<void> { await this.page.goto('${url}'); } /** * Wait for page to be fully loaded */ async waitForPageLoad(): Promise<void> { await this.page.waitForLoadState('networkidle'); } ${actions} } `; } generateJavaScriptPOM(className, properties, url) { const propInitializations = properties .map((p) => ` this.${p.propertyName} = page.${p.locator}; // ${p.description}`) .join("\n"); const actions = this.generateJavaScriptActions(properties); return `const { expect } = require('@playwright/test'); /** * Page Object Model for: ${url} * Generated by LocatorLabs MCP * Elements: ${properties.length} */ class ${className} { /** * @param {import('@playwright/test').Page} page */ constructor(page) { this.page = page; ${propInitializations} } /** * Navigate to this page */ async navigate() { await this.page.goto('${url}'); } /** * Wait for page to be fully loaded */ async waitForPageLoad() { await this.page.waitForLoadState('networkidle'); } ${actions} } module.exports = { ${className} }; `; } generatePythonPOM(className, properties, url) { const propInitializations = properties .map((p) => { const pyLocator = this.toPythonLocator(p.locator); const pyName = this.toSnakeCase(p.propertyName); return ` self.${pyName} = page.${pyLocator} # ${p.description}`; }) .join("\n"); const actions = this.generatePythonActions(properties); return `""" Page Object Model for: ${url} Generated by LocatorLabs MCP Elements: ${properties.length} """ from playwright.sync_api import Page, expect class ${className}: """Page Object for ${className}""" def __init__(self, page: Page) -> None: self.page = page ${propInitializations} def navigate(self) -> None: """Navigate to this page""" self.page.goto('${url}') def wait_for_page_load(self) -> None: """Wait for page to be fully loaded""" self.page.wait_for_load_state('networkidle') ${actions} `; } generateTypeScriptActions(properties) { const actions = []; // Find form inputs and generate fill methods const inputs = properties.filter((p) => p.elementType === "input" || p.elementType === "textarea"); if (inputs.length > 0) { const params = inputs.map((p) => `${p.propertyName}: string`).join(", "); const fills = inputs.map((p) => ` await this.${p.propertyName}.fill(${p.propertyName});`).join("\n"); actions.push(` /** * Fill all form fields */ async fillForm(${params}): Promise<void> { ${fills} }`); } // Find submit button const submitBtn = properties.find((p) => p.propertyName.toLowerCase().includes("submit") || p.propertyName.toLowerCase().includes("login") || p.propertyName.toLowerCase().includes("signup")); if (submitBtn) { actions.push(` /** * Click the submit button */ async submit(): Promise<void> { await this.${submitBtn.propertyName}.click(); }`); } return actions.join("\n\n"); } generateJavaScriptActions(properties) { const actions = []; const inputs = properties.filter((p) => p.elementType === "input" || p.elementType === "textarea"); if (inputs.length > 0) { const params = inputs.map((p) => p.propertyName).join(", "); const fills = inputs.map((p) => ` await this.${p.propertyName}.fill(${p.propertyName});`).join("\n"); actions.push(` /** * Fill all form fields */ async fillForm(${params}) { ${fills} }`); } const submitBtn = properties.find((p) => p.propertyName.toLowerCase().includes("submit") || p.propertyName.toLowerCase().includes("login")); if (submitBtn) { actions.push(` /** * Click the submit button */ async submit() { await this.${submitBtn.propertyName}.click(); }`); } return actions.join("\n\n"); } generatePythonActions(properties) { const actions = []; const inputs = properties.filter((p) => p.elementType === "input" || p.elementType === "textarea"); if (inputs.length > 0) { const params = inputs.map((p) => `${this.toSnakeCase(p.propertyName)}: str`).join(", "); const fills = inputs .map((p) => ` self.${this.toSnakeCase(p.propertyName)}.fill(${this.toSnakeCase(p.propertyName)})`) .join("\n"); actions.push(` def fill_form(self, ${params}) -> None: """Fill all form fields""" ${fills}`); } const submitBtn = properties.find((p) => p.propertyName.toLowerCase().includes("submit") || p.propertyName.toLowerCase().includes("login")); if (submitBtn) { actions.push(` def submit(self) -> None: """Click the submit button""" self.${this.toSnakeCase(submitBtn.propertyName)}.click()`); } return actions.join("\n\n"); } toPythonLocator(locator) { return locator .replace(/getByRole/g, "get_by_role") .replace(/getByText/g, "get_by_text") .replace(/getByTestId/g, "get_by_test_id") .replace(/getByPlaceholder/g, "get_by_placeholder") .replace(/getByLabel/g, "get_by_label"); } toSnakeCase(str) { return str .replace(/([A-Z])/g, "_$1") .toLowerCase() .replace(/^_/, ""); } getSeleniumLocator(el) { if (el.id) return `By.id("${el.id}")`; if (el.name) return `By.name("${el.name}")`; return `By.cssSelector("${el.tagName}")`; } getXPath(el) { if (el.id) return `//*[@id="${el.id}"]`; if (el.name) return `//*[@name="${el.name}"]`; return `//${el.tagName}`; } getCssSelector(el) { if (el.id) return `#${el.id}`; if (el.className) { const firstClass = el.className.split(" ")[0]; return `${el.tagName}.${firstClass}`; } return el.tagName; } generateJavaSeleniumPOM(className, properties, url) { const fields = properties .map((p) => { const locator = p.id ? `@FindBy(id = "${p.id}")` : p.name ? `@FindBy(name = "${p.name}")` : `@FindBy(css = "${p.cssSelector || p.elementType}")`; return ` ${locator}\n private WebElement ${p.propertyName}; // ${p.description}`; }) .join("\n\n"); const getters = properties .map((p) => ` public WebElement get${this.capitalize(p.propertyName)}() {\n return ${p.propertyName};\n }`) .join("\n\n"); const actions = this.generateJavaSeleniumActions(properties); return `package pages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; /** * Page Object Model for: ${url} * Generated by LocatorLabs MCP * Elements: ${properties.length} */ public class ${className} { private WebDriver driver; ${fields} public ${className}(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); } /** * Navigate to this page */ public void navigate() { driver.get("${url}"); } ${getters} ${actions} } `; } generatePythonSeleniumPOM(className, properties, url) { const locators = properties .map((p) => { const snakeName = this.toSnakeCase(p.propertyName).toUpperCase(); if (p.id) return ` ${snakeName} = (By.ID, "${p.id}") # ${p.description}`; if (p.name) return ` ${snakeName} = (By.NAME, "${p.name}") # ${p.description}`; return ` ${snakeName} = (By.CSS_SELECTOR, "${p.cssSelector || p.elementType}") # ${p.description}`; }) .join("\n"); const methods = properties .map((p) => { const snakeName = this.toSnakeCase(p.propertyName); const locatorName = snakeName.toUpperCase(); return ` def get_${snakeName}(self):\n return self.driver.find_element(*self.${locatorName})`; }) .join("\n\n"); const actions = this.generatePythonSeleniumActions(properties); return `""" Page Object Model for: ${url} Generated by LocatorLabs MCP Elements: ${properties.length} """ from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class ${className}: """Page Object for ${className}""" URL = "${url}" # Locators ${locators} def __init__(self, driver: WebDriver) -> None: self.driver = driver self.wait = WebDriverWait(driver, 10) def navigate(self) -> None: """Navigate to this page""" self.driver.get(self.URL) ${methods} ${actions} `; } generateCSharpSeleniumPOM(className, properties, url) { const fields = properties .map((p) => { const locator = p.id ? `[FindsBy(How = How.Id, Using = "${p.id}")]` : p.name ? `[FindsBy(How = How.Name, Using = "${p.name}")]` : `[FindsBy(How = How.CssSelector, Using = "${p.cssSelector || p.elementType}")]`; return ` ${locator}\n private IWebElement ${this.capitalize(p.propertyName)}; // ${p.description}`; }) .join("\n\n"); const getters = properties .map((p) => ` public IWebElement Get${this.capitalize(p.propertyName)}() => ${this.capitalize(p.propertyName)};`) .join("\n\n"); const actions = this.generateCSharpSeleniumActions(properties); return `using OpenQA.Selenium; using OpenQA.Selenium.Support.PageObjects; using OpenQA.Selenium.Support.UI; namespace Pages { /// <summary> /// Page Object Model for: ${url} /// Generated by LocatorLabs MCP /// Elements: ${properties.length} /// </summary> public class ${className} { private readonly IWebDriver _driver; private readonly WebDriverWait _wait; ${fields} public ${className}(IWebDriver driver) { _driver = driver; _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); PageFactory.InitElements(driver, this); } /// <summary> /// Navigate to this page /// </summary> public void Navigate() { _driver.Navigate().GoToUrl("${url}"); } ${getters} ${actions} } } `; } capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } generateJavaSeleniumActions(properties) { const actions = []; const inputs = properties.filter((p) => p.elementType === "input" || p.elementType === "textarea"); if (inputs.length > 0) { const params = inputs.map((p) => `String ${p.propertyName}`).join(", "); const fills = inputs.map((p) => ` ${p.propertyName}.sendKeys(${p.propertyName});`).join("\n"); actions.push(` /** * Fill all form fields */ public void fillForm(${params}) { ${fills} }`); } const submitBtn = properties.find((p) => p.propertyName.toLowerCase().includes("submit") || p.propertyName.toLowerCase().includes("login") || p.propertyName.toLowerCase().includes("button")); if (submitBtn) { actions.push(` /** * Click submit button */ public void submit() { ${submitBtn.propertyName}.click(); }`); } return actions.join("\n\n"); } generatePythonSeleniumActions(properties) { const actions = []; const inputs = properties.filter((p) => p.elementType === "input" || p.elementType === "textarea"); if (inputs.length > 0) { const params = inputs.map((p) => `${this.toSnakeCase(p.propertyName)}: str`).join(", "); const fills = inputs .map((p) => ` self.get_${this.toSnakeCase(p.propertyName)}().send_keys(${this.toSnakeCase(p.propertyName)})`) .join("\n"); actions.push(` def fill_form(self, ${params}) -> None: """Fill all form fields""" ${fills}`); } const submitBtn = properties.find((p) => p.propertyName.toLowerCase().includes("submit") || p.propertyName.toLowerCase().includes("login")); if (submitBtn) { actions.push(` def submit(self) -> None: """Click submit button""" self.get_${this.toSnakeCase(submitBtn.propertyName)}().click()`); } return actions.join("\n\n"); } generateCSharpSeleniumActions(properties) { const actions = []; const inputs = properties.filter((p) => p.elementType === "input" || p.elementType === "textarea"); if (inputs.length > 0) { const params = inputs.map((p) => `string ${p.propertyName}`).join(", "); const fills = inputs.map((p) => ` ${this.capitalize(p.propertyName)}.SendKeys(${p.propertyName});`).join("\n"); actions.push(` /// <summary> /// Fill all form fields /// </summary> public void FillForm(${params}) { ${fills} }`); } const submitBtn = properties.find((p) => p.propertyName.toLowerCase().includes("submit") || p.propertyName.toLowerCase().includes("login")); if (submitBtn) { actions.push(` /// <summary> /// Click submit button /// </summary> public void Submit() { ${this.capitalize(submitBtn.propertyName)}.Click(); }`); } return actions.join("\n\n"); } } exports.GeneratePOMTool = GeneratePOMTool; //# sourceMappingURL=generate-pom.js.map

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/naveenanimation20/locatorlabs-mcp'

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