# SkillPatch skill: selenium-skill

Generates production-grade Selenium WebDriver automation scripts and tests in multiple languages including Java, Python, JavaScript, C#, Ruby, and PHP. Supports both local execution and cloud-based cross-browser testing via TestMu AI / LambdaTest with 3000+ browser/OS combinations. Provides structured workflows for language detection, execution target selection, locator strategies, wait patterns, and anti-pattern avoidance.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/selenium-skill
curl -sSL https://skillpatch.dev/install_skill/selenium-skill | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (12)

- `SKILL.md`
- `reference/cloud-integration.md`
- `reference/csharp-patterns.md`
- `reference/debugging-common-issues.md`
- `reference/javascript-patterns.md`
- `reference/page-object-model.md`
- `reference/php-patterns.md`
- `reference/playbook.md`
- `reference/python-patterns.md`
- `reference/ruby-patterns.md`
- `scripts/scaffold-project.sh`
- `templates/pom.xml`


### `SKILL.md`

````markdown
---
name: selenium-skill
description: >
  Generates production-grade Selenium WebDriver automation scripts and tests
  in Java, Python, JavaScript, C#, Ruby, or PHP. Supports local execution
  and TestMu AI cloud with 3000+ browser/OS combinations. Use when the user
  asks to write Selenium tests, automate with WebDriver, run cross-browser
  tests on Selenium Grid, or mentions "Selenium", "WebDriver", "RemoteWebDriver",
  "ChromeDriver", "GeckoDriver". Triggers on: "Selenium", "WebDriver",
  "browser automation", "Selenium Grid", "cross-browser", "TestMu", "LambdaTest".
languages:
  - Java
  - Python
  - JavaScript
  - C#
  - Ruby
category: e2e-testing
license: MIT
metadata:
  author: TestMu AI
  version: "1.0"
---

# Selenium Automation Skill

You are a senior QA automation architect. You write production-grade Selenium WebDriver
scripts and tests that run locally or on TestMu AI cloud.

## Step 1 — Execution Target

```
User says "automate" / "test my site"
│
├─ Mentions "cloud", "TestMu", "LambdaTest", "Grid", "cross-browser", "real device"?
│  └─ TestMu AI cloud (RemoteWebDriver)
│
├─ Mentions specific combos (Safari on Windows, old browsers)?
│  └─ Suggest TestMu AI cloud
│
├─ Mentions "locally", "my machine", "ChromeDriver"?
│  └─ Local execution
│
└─ Ambiguous? → Default local, mention cloud for broader coverage
```

## Step 2 — Language Detection

| Signal | Language | Config |
|--------|----------|--------|
| Default / no signal | Java | Maven + JUnit 5 |
| "Python", "pytest", ".py" | Python | pip + pytest |
| "JavaScript", "Node", ".js" | JavaScript | npm + Mocha/Jest |
| "C#", ".NET", "NUnit" | C# | NuGet + NUnit |
| "Ruby", ".rb", "RSpec" | Ruby | gem + RSpec |
| "PHP", "Codeception" | PHP | Composer + PHPUnit |

For non-Java languages → read `reference/<language>-patterns.md`

## Step 3 — Scope

| Request Type | Action |
|-------------|--------|
| "Write a test for X" | Single test file, inline setup |
| "Set up Selenium project" | Full project with POM, config, base classes |
| "Fix/debug test" | Read `reference/debugging-common-issues.md` |
| "Run on cloud" | Read `reference/cloud-integration.md` |

## Core Patterns — Java (Default)

### Locator Priority

```
1. By.id("element-id")           ← Most stable
2. By.name("field-name")         ← Form elements
3. By.cssSelector(".class")      ← Fast, readable
4. By.xpath("//div[@data-testid]") ← Last resort
```

**NEVER use:** fragile XPaths like `//div[3]/span[2]/a`, absolute paths.

### Wait Strategy — CRITICAL

```java
// ✅ ALWAYS use explicit waits
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("submit")));

// ❌ NEVER use Thread.sleep() or implicit waits mixed with explicit
Thread.sleep(3000); // FORBIDDEN
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); // Don't mix
```

### Anti-Patterns

| Bad | Good | Why |
|-----|------|-----|
| `Thread.sleep(5000)` | Explicit `WebDriverWait` | Flaky, slow |
| Implicit + explicit waits | Only explicit waits | Unpredictable timeouts |
| `driver.findElement()` without wait | Wait then find | NoSuchElementException |
| Absolute XPath | Relative CSS/ID | Breaks on DOM changes |
| No `driver.quit()` | Always `quit()` in finally/teardown | Leaks browsers |

### Basic Test Structure

```java
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.junit.jupiter.api.*;
import java.time.Duration;

public class LoginTest {
    private WebDriver driver;
    private WebDriverWait wait;

    @BeforeEach
    void setUp() {
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        driver.manage().window().maximize();
    }

    @Test
    void testLogin() {
        driver.get("https://example.com/login");
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("username")))
            .sendKeys("user@test.com");
        driver.findElement(By.id("password")).sendKeys("password123");
        driver.findElement(By.cssSelector("button[type='submit']")).click();
        wait.until(ExpectedConditions.urlContains("/dashboard"));
        Assertions.assertTrue(driver.getTitle().contains("Dashboard"));
    }

    @AfterEach
    void tearDown() {
        if (driver != null) driver.quit();
    }
}
```

### Page Object Model — Quick Example

```java
// pages/LoginPage.java
public class LoginPage {
    private WebDriver driver;
    private WebDriverWait wait;

    private By usernameField = By.id("username");
    private By passwordField = By.id("password");
    private By submitButton  = By.cssSelector("button[type='submit']");

    public LoginPage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    public void login(String username, String password) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(usernameField))
            .sendKeys(username);
        driver.findElement(passwordField).sendKeys(password);
        driver.findElement(submitButton).click();
    }
}
```

### TestMu AI Cloud — Quick Setup

```java
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.HashMap;

String username = System.getenv("LT_USERNAME");
String accessKey = System.getenv("LT_ACCESS_KEY");
String hub = "https://" + username + ":" + accessKey + "@hub.lambdatest.com/wd/hub";

DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("browserName", "Chrome");
caps.setCapability("browserVersion", "latest");
HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("platform", "Windows 11");
ltOptions.put("build", "Selenium Build");
ltOptions.put("name", "My Test");
ltOptions.put("video", true);
ltOptions.put("network", true);
caps.setCapability("LT:Options", ltOptions);

WebDriver driver = new RemoteWebDriver(new URL(hub), caps);
```

### Test Status Reporting

```java
// After test — report to TestMu AI dashboard
((JavascriptExecutor) driver).executeScript(
    "lambda-status=" + (testPassed ? "passed" : "failed")
);
```

## Validation Workflow

1. **Locators**: No absolute XPath, prefer ID/CSS
2. **Waits**: Only explicit WebDriverWait, zero Thread.sleep()
3. **Cleanup**: driver.quit() in @AfterEach/teardown
4. **Cloud**: LT_USERNAME + LT_ACCESS_KEY from env vars
5. **POM**: Locators in page class, assertions in test class

## Quick Reference

| Task | Command/Code |
|------|-------------|
| Run with Maven | `mvn test` |
| Run single test | `mvn test -Dtest=LoginTest` |
| Run with Gradle | `./gradlew test` |
| Parallel (TestNG) | `<suite parallel="tests" thread-count="5">` |
| Screenshots | `((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE)` |
| Actions API | `new Actions(driver).moveToElement(el).click().perform()` |
| Select dropdown | `new Select(driver.findElement(By.id("dropdown"))).selectByValue("1")` |
| Handle alert | `driver.switchTo().alert().accept()` |
| Switch iframe | `driver.switchTo().frame("frameName")` |
| New tab/window | `driver.switchTo().newWindow(WindowType.TAB)` |

## Reference Files

| File | When to Read |
|------|-------------|
| `reference/cloud-integration.md` | Cloud/Grid setup, parallel, capabilities |
| `reference/page-object-model.md` | Full POM with base classes, factories |
| `reference/python-patterns.md` | Python + pytest-selenium |
| `reference/javascript-patterns.md` | Node.js + Mocha/Jest |
| `reference/csharp-patterns.md` | C# + NUnit/xUnit |
| `reference/ruby-patterns.md` | Ruby + RSpec/Capybara |
| `reference/php-patterns.md` | PHP + Composer + PHPUnit |
| `reference/debugging-common-issues.md` | Stale elements, timeouts, flaky |

## Advanced Playbook

For production-grade patterns, see `reference/playbook.md`:

| Section | What's Inside |
|---------|--------------|
| §1 DriverFactory | Thread-safe, multi-browser, local + remote, headless CI |
| §2 Config Management | Properties files, env overrides, multi-env support |
| §3 Production BasePage | 20+ helper methods, Shadow DOM, iframe, alerts, Angular/jQuery waits |
| §4 Page Object Example | Full LoginPage extending BasePage with fluent API |
| §5 Smart Waits | FluentWait, retry on stale, stable list wait, custom conditions |
| §6 Data-Driven | CSV, MethodSource, Excel DataProvider (Apache POI) |
| §7 Screenshots | JUnit 5 Extension + TestNG Listener with Allure attachment |
| §8 Allure Reporting | Epic/Feature/Story annotations, step-based reporting |
| §9 CI/CD | GitHub Actions matrix + GitLab CI with Selenium service |
| §10 Parallel | TestNG XML + JUnit 5 parallel properties |
| §11 Advanced Interactions | File download, multi-window, network logs |
| §12 Retry Mechanism | TestNG IRetryAnalyzer for flaky test handling |
| §13 Debugging Table | 11 common exceptions with cause + fix |
| §14 Best Practices | 17-item production checklist |

````


### `reference/cloud-integration.md`

````markdown
# Selenium — TestMu AI Cloud Integration

For full device catalog, capabilities, and LT:Options reference, see [shared/testmu-cloud-reference.md](../../shared/testmu-cloud-reference.md).

## Hub URL Format

```
https://{LT_USERNAME}:{LT_ACCESS_KEY}@hub.lambdatest.com/wd/hub
```

## DesiredCapabilities Structure

```java
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("browserName", "Chrome");      // Chrome, MicrosoftEdge, Firefox, Safari
caps.setCapability("browserVersion", "latest");    // or specific: "120.0"

HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("platform", "Windows 11");
ltOptions.put("build", "Selenium Build");
ltOptions.put("name", "Test Name");
ltOptions.put("user", System.getenv("LT_USERNAME"));
ltOptions.put("accessKey", System.getenv("LT_ACCESS_KEY"));
ltOptions.put("video", true);
ltOptions.put("network", true);
ltOptions.put("console", true);
ltOptions.put("visual", true);
ltOptions.put("resolution", "1920x1080");
ltOptions.put("tunnel", false);           // true for localhost
ltOptions.put("tunnelName", "my-tunnel"); // named tunnel
ltOptions.put("geoLocation", "US");       // geo-testing
caps.setCapability("LT:Options", ltOptions);

WebDriver driver = new RemoteWebDriver(new URL(hub), caps);
```

## Parallel Execution — TestNG

```xml
<!-- testng.xml -->
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Cross Browser" parallel="tests" thread-count="5">
  <test name="Chrome Windows">
    <parameter name="browser" value="Chrome"/>
    <parameter name="version" value="latest"/>
    <parameter name="platform" value="Windows 11"/>
    <classes><class name="tests.LoginTest"/></classes>
  </test>
  <test name="Firefox Mac">
    <parameter name="browser" value="Firefox"/>
    <parameter name="version" value="latest"/>
    <parameter name="platform" value="macOS Sequoia"/>
    <classes><class name="tests.LoginTest"/></classes>
  </test>
</suite>
```

```java
// Base test with @Parameters
public class BaseTest {
    protected WebDriver driver;

    @Parameters({"browser", "version", "platform"})
    @BeforeMethod
    public void setUp(String browser, String version, String platform) throws Exception {
        String hub = "https://" + System.getenv("LT_USERNAME") + ":"
                   + System.getenv("LT_ACCESS_KEY") + "@hub.lambdatest.com/wd/hub";

        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability("browserName", browser);
        caps.setCapability("browserVersion", version);
        HashMap<String, Object> ltOptions = new HashMap<>();
        ltOptions.put("platform", platform);
        ltOptions.put("build", "Parallel Build");
        ltOptions.put("name", browser + " " + platform);
        ltOptions.put("video", true);
        caps.setCapability("LT:Options", ltOptions);

        driver = new RemoteWebDriver(new URL(hub), caps);
        driver.manage().window().maximize();
    }

    @AfterMethod
    public void tearDown(ITestResult result) {
        if (driver != null) {
            String status = result.isSuccess() ? "passed" : "failed";
            ((JavascriptExecutor) driver).executeScript("lambda-status=" + status);
            driver.quit();
        }
    }
}
```

## Parallel Execution — JUnit 5

```properties
# junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=5
```

## Mobile Testing on Cloud

```java
// Android
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("browserName", "Chrome");
caps.setCapability("browserVersion", "latest");
HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("platformName", "android");
ltOptions.put("deviceName", "Pixel 7");
ltOptions.put("platformVersion", "13");
ltOptions.put("isRealMobile", true);
caps.setCapability("LT:Options", ltOptions);

// iOS — IMPORTANT: use Safari, not Chrome
DesiredCapabilities iosCaps = new DesiredCapabilities();
iosCaps.setCapability("browserName", "Safari");
iosCaps.setCapability("browserVersion", "latest");
HashMap<String, Object> iosOptions = new HashMap<>();
iosOptions.put("platformName", "ios");
iosOptions.put("deviceName", "iPhone 16");
iosOptions.put("platformVersion", "18");
iosOptions.put("isRealMobile", true);
iosCaps.setCapability("LT:Options", iosOptions);
```

## Test Status Reporting

```java
// Java
((JavascriptExecutor) driver).executeScript("lambda-status=" + (passed ? "passed" : "failed"));

// With remark
((JavascriptExecutor) driver).executeScript(
    "lambda-action: {\"action\": \"setTestStatus\", \"arguments\": {\"status\": \""
    + status + "\", \"remark\": \"" + remark + "\"}}"
);
```

````


### `reference/csharp-patterns.md`

````markdown
# Selenium — C# Patterns

## Setup

```xml
<!-- .csproj -->
<PackageReference Include="Selenium.WebDriver" Version="4.*" />
<PackageReference Include="Selenium.Support" Version="4.*" />
<PackageReference Include="NUnit" Version="4.*" />
<PackageReference Include="NUnit3TestAdapter" Version="4.*" />
```

## Basic Test (NUnit)

```csharp
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using NUnit.Framework;

[TestFixture]
public class LoginTests
{
    private IWebDriver driver;
    private WebDriverWait wait;

    [SetUp]
    public void SetUp()
    {
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
        driver.Manage().Window.Maximize();
    }

    [Test]
    public void TestLogin()
    {
        driver.Navigate().GoToUrl("https://example.com/login");
        wait.Until(d => d.FindElement(By.Id("username"))).SendKeys("user@test.com");
        driver.FindElement(By.Id("password")).SendKeys("password123");
        driver.FindElement(By.CssSelector("button[type='submit']")).Click();
        wait.Until(d => d.Url.Contains("/dashboard"));
        Assert.That(driver.Title, Does.Contain("Dashboard"));
    }

    [TearDown]
    public void TearDown()
    {
        driver?.Quit();
    }
}
```

## TestMu AI Cloud (C#)

```csharp
var options = new ChromeOptions();
var ltOptions = new Dictionary<string, object>
{
    { "platform", "Windows 11" },
    { "build", "C# Build" },
    { "name", "C# Test" },
    { "user", Environment.GetEnvironmentVariable("LT_USERNAME") },
    { "accessKey", Environment.GetEnvironmentVariable("LT_ACCESS_KEY") },
    { "video", true },
    { "network", true }
};
options.AddAdditionalOption("LT:Options", ltOptions);
driver = new RemoteWebDriver(
    new Uri("https://hub.lambdatest.com/wd/hub"), options);
```

````


### `reference/debugging-common-issues.md`

````markdown
# Selenium — Debugging Common Issues

## StaleElementReferenceException

**Cause**: DOM re-rendered after element was found.

```java
// ❌ Bad
WebElement el = driver.findElement(By.id("btn"));
// ... page reloads or AJAX updates ...
el.click(); // StaleElementReferenceException

// ✅ Good — Re-locate element
wait.until(ExpectedConditions.elementToBeClickable(By.id("btn"))).click();
```

## NoSuchElementException

**Cause**: Element not in DOM or not visible yet.

```java
// ❌ Bad
driver.findElement(By.id("dynamic-element")).click();

// ✅ Good — Wait for it
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("dynamic-element"))).click();
```

## ElementClickInterceptedException

**Cause**: Another element covers the target.

```java
// ✅ Use JavaScript click
WebElement el = wait.until(ExpectedConditions.elementToBeClickable(By.id("btn")));
((JavascriptExecutor) driver).executeScript("arguments[0].click();", el);

// ✅ Or scroll into view first
((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", el);
el.click();
```

## Timeouts

```java
// Page load timeout
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));

// Script timeout (for async JS)
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(30));

// Custom wait with polling
new WebDriverWait(driver, Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(500))
    .ignoring(NoSuchElementException.class)
    .until(ExpectedConditions.visibilityOfElementLocated(By.id("result")));
```

## Iframe Handling

```java
// Switch to iframe
driver.switchTo().frame("iframeName");
// or by index
driver.switchTo().frame(0);
// or by WebElement
driver.switchTo().frame(driver.findElement(By.cssSelector("iframe.content")));
// Switch back to main
driver.switchTo().defaultContent();
```

## Alert Handling

```java
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
String alertText = alert.getText();
alert.accept(); // or alert.dismiss();
```

## File Upload

```java
WebElement upload = driver.findElement(By.cssSelector("input[type='file']"));
upload.sendKeys("/path/to/file.pdf");
```

## Screenshots on Failure

```java
@AfterEach
void tearDown(TestInfo testInfo) {
    if (driver != null) {
        try {
            File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            Files.copy(screenshot.toPath(),
                Path.of("screenshots/" + testInfo.getDisplayName() + ".png"));
        } catch (Exception e) { /* ignore */ }
        driver.quit();
    }
}
```

````


### `reference/javascript-patterns.md`

````markdown
# Selenium — JavaScript/Node.js Patterns

## Setup

```bash
npm install selenium-webdriver mocha chai
```

## Basic Test (Mocha)

```javascript
const { Builder, By, until } = require('selenium-webdriver');
const { expect } = require('chai');

describe('Login', function() {
    let driver;
    this.timeout(30000);

    before(async () => {
        driver = await new Builder().forBrowser('chrome').build();
    });

    it('should login successfully', async () => {
        await driver.get('https://example.com/login');
        await driver.wait(until.elementLocated(By.id('username')), 10000);
        await driver.findElement(By.id('username')).sendKeys('user@test.com');
        await driver.findElement(By.id('password')).sendKeys('password123');
        await driver.findElement(By.css("button[type='submit']")).click();
        await driver.wait(until.urlContains('/dashboard'), 10000);
        const title = await driver.getTitle();
        expect(title).to.include('Dashboard');
    });

    after(async () => {
        if (driver) await driver.quit();
    });
});
```

## TestMu AI Cloud (Node.js)

```javascript
const { Builder } = require('selenium-webdriver');

const capabilities = {
    browserName: 'Chrome',
    browserVersion: 'latest',
    'LT:Options': {
        platform: 'Windows 11',
        build: 'Node Build',
        name: 'Node Test',
        user: process.env.LT_USERNAME,
        accessKey: process.env.LT_ACCESS_KEY,
        video: true,
        network: true,
    }
};

const driver = await new Builder()
    .usingServer(`https://${process.env.LT_USERNAME}:${process.env.LT_ACCESS_KEY}@hub.lambdatest.com/wd/hub`)
    .withCapabilities(capabilities)
    .build();
```

````


### `reference/page-object-model.md`

````markdown
# Selenium — Page Object Model

## Base Page Class

```java
public abstract class BasePage {
    protected WebDriver driver;
    protected WebDriverWait wait;

    public BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    protected WebElement waitForVisible(By locator) {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }

    protected WebElement waitForClickable(By locator) {
        return wait.until(ExpectedConditions.elementToBeClickable(locator));
    }

    protected void type(By locator, String text) {
        WebElement el = waitForVisible(locator);
        el.clear();
        el.sendKeys(text);
    }

    protected void click(By locator) {
        waitForClickable(locator).click();
    }

    protected String getText(By locator) {
        return waitForVisible(locator).getText();
    }

    protected boolean isDisplayed(By locator) {
        try {
            return waitForVisible(locator).isDisplayed();
        } catch (Exception e) {
            return false;
        }
    }
}
```

## Concrete Page Object

```java
public class LoginPage extends BasePage {
    private By usernameField = By.id("username");
    private By passwordField = By.id("password");
    private By loginButton = By.cssSelector("button[type='submit']");
    private By errorMessage = By.cssSelector(".error-message");

    public LoginPage(WebDriver driver) {
        super(driver);
    }

    public LoginPage navigate() {
        driver.get("https://example.com/login");
        waitForVisible(usernameField);
        return this;
    }

    public DashboardPage loginAs(String username, String password) {
        type(usernameField, username);
        type(passwordField, password);
        click(loginButton);
        return new DashboardPage(driver);
    }

    public String getErrorMessage() {
        return getText(errorMessage);
    }
}
```

## Page Factory Pattern

```java
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class LoginPage {
    @FindBy(id = "username") private WebElement usernameField;
    @FindBy(id = "password") private WebElement passwordField;
    @FindBy(css = "button[type='submit']") private WebElement loginButton;

    public LoginPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public void login(String user, String pass) {
        usernameField.sendKeys(user);
        passwordField.sendKeys(pass);
        loginButton.click();
    }
}
```

````


### `reference/php-patterns.md`

````markdown
# Selenium — PHP Patterns

## Setup

```bash
composer require phpunit/phpunit php-webdriver/webdriver
```

```json
// composer.json (relevant part)
{
  "require-dev": {
    "phpunit/phpunit": "^10",
    "php-webdriver/webdriver": "^1.14"
  },
  "autoload": {
    "classmap": ["src/", "tests/"]
  }
}
```

## Basic Test (PHPUnit)

```php
<?php

use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverExpectedCondition;

class LoginTest extends TestCase
{
    private $driver;
    private $wait;

    protected function setUp(): void
    {
        $caps = DesiredCapabilities::chrome();
        $this->driver = RemoteWebDriver::create('http://localhost:9515', $caps);
        $this->driver->manage()->window()->maximize();
        $this->wait = $this->driver->wait(10);
    }

    public function testLogin(): void
    {
        $this->driver->get('https://example.com/login');
        $this->wait->until(
            WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::id('username'))
        );
        $this->driver->findElement(WebDriverBy::id('username'))->sendKeys('user@test.com');
        $this->driver->findElement(WebDriverBy::id('password'))->sendKeys('password123');
        $this->driver->findElement(WebDriverBy::cssSelector('button[type="submit"]'))->click();
        $this->wait->until(WebDriverExpectedCondition::urlContains('/dashboard'));
        $this->assertStringContainsString('Dashboard', $this->driver->getTitle());
    }

    protected function tearDown(): void
    {
        if ($this->driver) {
            $this->driver->quit();
        }
    }
}
```

## TestMu AI Cloud (PHP)

```php
<?php

use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\WebDriverCapabilityType;

$username = getenv('LT_USERNAME');
$accessKey = getenv('LT_ACCESS_KEY');
$hubUrl = "https://{$username}:{$accessKey}@hub.lambdatest.com/wd/hub";

$capabilities = [
    WebDriverCapabilityType::BROWSER_NAME => 'Chrome',
    WebDriverCapabilityType::BROWSER_VERSION => 'latest',
    'LT:Options' => [
        'platform' => 'Windows 11',
        'build' => 'PHP Build',
        'name' => 'PHP Test',
        'video' => true,
        'network' => true,
    ],
];

$driver = RemoteWebDriver::create($hubUrl, $capabilities);
$driver->manage()->window()->maximize();
```

## Page Object in PHP

```php
<?php

use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverExpectedCondition;

class LoginPage
{
    private $driver;
    private $wait;

    private static $usernameField = WebDriverBy::id('username');
    private static $passwordField = WebDriverBy::id('password');
    private static $submitButton = WebDriverBy::cssSelector('button[type="submit"]');

    public function __construct(RemoteWebDriver $driver)
    {
        $this->driver = $driver;
        $this->wait = $driver->wait(10);
    }

    public function login(string $username, string $password): void
    {
        $this->wait->until(
            WebDriverExpectedCondition::visibilityOfElementLocated(self::$usernameField)
        );
        $this->driver->findElement(self::$usernameField)->sendKeys($username);
        $this->driver->findElement(self::$passwordField)->sendKeys($password);
        $this->driver->findElement(self::$submitButton)->click();
    }
}
```

## Run Tests

```bash
./vendor/bin/phpunit tests/
```

## Notes

- Use explicit waits via `$driver->wait(seconds)` and `WebDriverExpectedCondition` — avoid `sleep()`.
- Always call `$driver->quit()` in `tearDown()`.
- For full device/capability reference, see [shared/testmu-cloud-reference.md](../../shared/testmu-cloud-reference.md).

````


### `reference/playbook.md`

````markdown
# Selenium WebDriver — Advanced Implementation Playbook

## §1 — Thread-Safe DriverFactory

```java
public class DriverFactory {
    private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();

    public static WebDriver getDriver() { return driver.get(); }

    public static void initDriver(String browser) {
        WebDriver d;
        switch (browser.toLowerCase()) {
            case "firefox":
                WebDriverManager.firefoxdriver().setup();
                d = new FirefoxDriver(firefoxOptions());
                break;
            case "edge":
                WebDriverManager.edgedriver().setup();
                d = new EdgeDriver(edgeOptions());
                break;
            case "safari":
                d = new SafariDriver();
                break;
            default:
                WebDriverManager.chromedriver().setup();
                d = new ChromeDriver(chromeOptions());
        }
        d.manage().window().maximize();
        d.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
        d.manage().timeouts().scriptTimeout(Duration.ofSeconds(30));
        driver.set(d);
    }

    public static void initRemoteDriver(String browser, String hubUrl) {
        MutableCapabilities caps;
        switch (browser.toLowerCase()) {
            case "firefox": caps = firefoxOptions(); break;
            case "edge":    caps = edgeOptions(); break;
            default:        caps = chromeOptions();
        }
        try {
            WebDriver d = new RemoteWebDriver(new URL(hubUrl), caps);
            d.manage().window().maximize();
            driver.set(d);
        } catch (MalformedURLException e) { throw new RuntimeException(e); }
    }

    private static ChromeOptions chromeOptions() {
        ChromeOptions opts = new ChromeOptions();
        opts.addArguments("--disable-notifications", "--disable-popup-blocking");
        if (Boolean.parseBoolean(System.getProperty("headless", "false"))
                || System.getenv("CI") != null) {
            opts.addArguments("--headless=new", "--no-sandbox", "--disable-dev-shm-usage",
                "--disable-gpu", "--window-size=1920,1080");
        }
        return opts;
    }

    private static FirefoxOptions firefoxOptions() {
        FirefoxOptions opts = new FirefoxOptions();
        if (Boolean.parseBoolean(System.getProperty("headless", "false"))
                || System.getenv("CI") != null) {
            opts.addArguments("-headless");
        }
        return opts;
    }

    private static EdgeOptions edgeOptions() {
        EdgeOptions opts = new EdgeOptions();
        if (Boolean.parseBoolean(System.getProperty("headless", "false"))
                || System.getenv("CI") != null) {
            opts.addArguments("--headless=new");
        }
        return opts;
    }

    public static void quitDriver() {
        if (driver.get() != null) { driver.get().quit(); driver.remove(); }
    }
}
```

## §2 — Configuration Management

```java
// config.properties → src/test/resources/
// base.url=https://staging.example.com
// browser=chrome
// timeout.explicit=10
// timeout.pageload=30
// headless=false
// screenshot.on.failure=true
// retry.count=2

public class Config {
    private static final Properties props = new Properties();

    static {
        try (InputStream is = Config.class.getClassLoader()
                .getResourceAsStream("config.properties")) {
            props.load(is);
        } catch (IOException e) { throw new RuntimeException(e); }
        // System properties override file (for CI: -Dbase.url=https://prod.example.com)
        System.getProperties().forEach((k, v) -> props.setProperty(k.toString(), v.toString()));
    }

    public static String get(String key) { return props.getProperty(key); }
    public static String get(String key, String def) { return props.getProperty(key, def); }
    public static int getInt(String key, int def) {
        String v = props.getProperty(key);
        return v != null ? Integer.parseInt(v) : def;
    }
    public static boolean getBool(String key, boolean def) {
        String v = props.getProperty(key);
        return v != null ? Boolean.parseBoolean(v) : def;
    }

    public static String baseUrl()       { return get("base.url", "https://localhost:3000"); }
    public static String browser()       { return get("browser", "chrome"); }
    public static int explicitTimeout()  { return getInt("timeout.explicit", 10); }
    public static int retryCount()       { return getInt("retry.count", 2); }
}
```

## §3 — Production BasePage

```java
public abstract class BasePage {
    protected WebDriver driver;
    protected WebDriverWait wait;
    protected Actions actions;

    public BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(Config.explicitTimeout()));
        this.actions = new Actions(driver);
    }

    // --- Core interactions ---
    protected WebElement find(By locator) {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }

    protected List<WebElement> findAll(By locator) {
        return wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(locator));
    }

    protected void click(By locator) {
        wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
    }

    protected void clickJS(By locator) {
        WebElement el = find(locator);
        ((JavascriptExecutor) driver).executeScript("arguments[0].click();", el);
    }

    protected void type(By locator, String text) {
        WebElement el = find(locator);
        el.clear();
        el.sendKeys(text);
    }

    protected String getText(By locator) { return find(locator).getText(); }

    protected boolean isDisplayed(By locator) {
        try { return driver.findElement(locator).isDisplayed(); }
        catch (NoSuchElementException e) { return false; }
    }

    // --- Advanced interactions ---
    protected void selectByVisibleText(By locator, String text) {
        new Select(find(locator)).selectByVisibleText(text);
    }

    protected void hover(By locator) {
        actions.moveToElement(find(locator)).perform();
    }

    protected void dragAndDrop(By source, By target) {
        actions.dragAndDrop(find(source), find(target)).perform();
    }

    protected void scrollToElement(By locator) {
        ((JavascriptExecutor) driver).executeScript(
            "arguments[0].scrollIntoView({behavior:'smooth',block:'center'});", find(locator));
    }

    protected void switchToFrame(By locator) { driver.switchTo().frame(find(locator)); }
    protected void switchToDefault() { driver.switchTo().defaultContent(); }

    protected void handleAlert(boolean accept) {
        wait.until(ExpectedConditions.alertIsPresent());
        Alert alert = driver.switchTo().alert();
        if (accept) alert.accept(); else alert.dismiss();
    }

    // --- Shadow DOM ---
    protected WebElement findInShadow(By hostLocator, String cssSelector) {
        SearchContext shadow = find(hostLocator).getShadowRoot();
        return shadow.findElement(By.cssSelector(cssSelector));
    }

    // --- Waits ---
    protected void waitForUrlContains(String text) {
        wait.until(ExpectedConditions.urlContains(text));
    }

    protected void waitForTextPresent(By locator, String text) {
        wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
    }

    protected void waitForPageLoad() {
        wait.until(d -> ((JavascriptExecutor) d)
            .executeScript("return document.readyState").equals("complete"));
    }

    protected void waitForAjax() {
        wait.until(d -> (Boolean) ((JavascriptExecutor) d)
            .executeScript("return typeof jQuery !== 'undefined' ? jQuery.active == 0 : true"));
    }

    protected void waitForAngular() {
        wait.until(d -> (Boolean) ((JavascriptExecutor) d).executeScript(
            "return window.getAllAngularTestabilities?.().every(t => t.isStable()) ?? true"));
    }
}
```

## §4 — Page Object Example with BasePage

```java
public class LoginPage extends BasePage {
    private final By usernameField = By.id("username");
    private final By passwordField = By.id("password");
    private final By submitButton  = By.cssSelector("button[type='submit']");
    private final By errorMessage  = By.cssSelector(".error-message");

    public LoginPage(WebDriver driver) { super(driver); }

    public LoginPage open() {
        driver.get(Config.baseUrl() + "/login");
        waitForPageLoad();
        return this;
    }

    public DashboardPage loginAs(String user, String pass) {
        type(usernameField, user);
        type(passwordField, pass);
        click(submitButton);
        return new DashboardPage(driver);
    }

    public LoginPage loginExpectingError(String user, String pass) {
        type(usernameField, user);
        type(passwordField, pass);
        click(submitButton);
        return this;
    }

    public String getErrorText() { return getText(errorMessage); }
    public boolean isErrorDisplayed() { return isDisplayed(errorMessage); }
}
```

## §5 — Smart Wait Strategies

```java
public class WaitUtils {
    private WebDriverWait wait;
    private WebDriver driver;

    public WaitUtils(WebDriver driver, int timeout) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(timeout));
    }

    /** Click with StaleElement retry */
    public void clickWithRetry(By locator, int maxRetries) {
        for (int i = 0; i < maxRetries; i++) {
            try {
                wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
                return;
            } catch (StaleElementReferenceException e) {
                if (i == maxRetries - 1) throw e;
            }
        }
    }

    /** FluentWait with custom polling */
    public WebElement fluentWait(By locator) {
        return new FluentWait<>(driver)
            .withTimeout(Duration.ofSeconds(30))
            .pollingEvery(Duration.ofMillis(500))
            .ignoring(NoSuchElementException.class)
            .ignoring(StaleElementReferenceException.class)
            .until(d -> d.findElement(locator));
    }

    /** Wait for element count to stabilize (dynamic lists) */
    public List<WebElement> waitForStableList(By locator) {
        wait.until(d -> {
            int first = d.findElements(locator).size();
            try { Thread.sleep(300); } catch (InterruptedException e) {}
            int second = d.findElements(locator).size();
            return first == second && first > 0;
        });
        return driver.findElements(locator);
    }

    /** Custom ExpectedCondition — element has attribute value */
    public static ExpectedCondition<Boolean> attributeContains(By locator, String attr, String value) {
        return d -> {
            try { return d.findElement(locator).getAttribute(attr).contains(value); }
            catch (Exception e) { return false; }
        };
    }
}
```

## §6 — Data-Driven Testing

```java
// JUnit 5 — CSV file
@ParameterizedTest(name = "Login: {0}/{2}")
@CsvFileSource(resources = "/testdata/login.csv", numLinesToSkip = 1)
void testLoginScenarios(String email, String password, String expected) {
    LoginPage loginPage = new LoginPage(driver).open();
    if ("success".equals(expected)) {
        loginPage.loginAs(email, password);
        assertTrue(driver.getCurrentUrl().contains("/dashboard"));
    } else {
        loginPage.loginExpectingError(email, password);
        assertTrue(loginPage.getErrorText().contains(expected));
    }
}

// JUnit 5 — Method source for complex objects
@ParameterizedTest
@MethodSource("registrationData")
void testRegistration(String name, String email, int age, boolean expected) {
    RegisterPage page = new RegisterPage(driver).open();
    page.fillForm(name, email, age);
    assertEquals(expected, page.isSubmitEnabled());
}

static Stream<Arguments> registrationData() {
    return Stream.of(
        Arguments.of("John", "john@test.com", 25, true),
        Arguments.of("", "invalid", -1, false),
        Arguments.of("Jane", "jane@test.com", 17, false)
    );
}

// TestNG — DataProvider with Excel (Apache POI)
@DataProvider(name = "excelData")
public Object[][] excelData() throws Exception {
    FileInputStream fis = new FileInputStream("src/test/resources/testdata.xlsx");
    Workbook wb = new XSSFWorkbook(fis);
    Sheet sheet = wb.getSheetAt(0);
    int rows = sheet.getPhysicalNumberOfRows();
    int cols = sheet.getRow(0).getPhysicalNumberOfCells();
    Object[][] data = new Object[rows - 1][cols];
    for (int i = 1; i < rows; i++)
        for (int j = 0; j < cols; j++)
            data[i - 1][j] = sheet.getRow(i).getCell(j).toString();
    wb.close();
    return data;
}
```

## §7 — Screenshot & Reporting on Failure

```java
// JUnit 5 Extension
public class ScreenshotExtension implements TestWatcher {
    @Override
    public void testFailed(ExtensionContext ctx, Throwable cause) {
        WebDriver driver = DriverFactory.getDriver();
        if (driver == null) return;
        File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        String name = ctx.getDisplayName().replaceAll("[^a-zA-Z0-9]", "_");
        Path dest = Path.of("target/screenshots", name + "_" + System.currentTimeMillis() + ".png");
        try {
            Files.createDirectories(dest.getParent());
            Files.copy(src.toPath(), dest);
            Allure.addAttachment("Screenshot", "image/png", Files.newInputStream(dest), ".png");
        } catch (IOException e) { e.printStackTrace(); }
    }
}

// TestNG Listener
public class TestListener implements ITestListener {
    @Override
    public void onTestFailure(ITestResult result) {
        WebDriver driver = ((BaseTest) result.getInstance()).getDriver();
        if (driver == null) return;
        String path = "target/screenshots/" + result.getName() + "_" + System.currentTimeMillis() + ".png";
        File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        try { FileUtils.copyFile(src, new File(path)); } catch (IOException e) {}
    }
}
```

## §8 — Allure Reporting

```java
@Epic("Authentication")
@Feature("Login")
@Story("Valid Credentials")
@Severity(SeverityLevel.CRITICAL)
@Test
void testValidLogin() {
    Allure.step("Navigate to login", () -> driver.get(Config.baseUrl() + "/login"));
    Allure.step("Enter credentials", () -> new LoginPage(driver).loginAs("admin", "pass"));
    Allure.step("Verify dashboard", () -> assertTrue(driver.getCurrentUrl().contains("/dashboard")));
}
```

```xml
<!-- pom.xml -->
<dependency>
  <groupId>io.qameta.allure</groupId>
  <artifactId>allure-junit5</artifactId>
  <version>2.25.0</version>
  <scope>test</scope>
</dependency>
```

## §9 — CI/CD Integration

```yaml
# GitHub Actions
name: Selenium Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        browser: [chrome, firefox, edge]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 17 }
      - name: Cache Maven
        uses: actions/cache@v4
        with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
      - name: Run tests
        run: mvn test -Dbrowser=${{ matrix.browser }} -Dheadless=true
        env:
          LT_USERNAME: ${{ secrets.LT_USERNAME }}
          LT_ACCESS_KEY: ${{ secrets.LT_ACCESS_KEY }}
      - name: Allure report
        run: mvn allure:report
        if: always()
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: results-${{ matrix.browser }}
          path: |
            target/surefire-reports/
            target/site/allure-maven-plugin/
            target/screenshots/
```

```yaml
# GitLab CI
selenium-tests:
  image: maven:3.9-eclipse-temurin-17
  stage: test
  services:
    - selenium/standalone-chrome:latest
  variables:
    SELENIUM_REMOTE_URL: http://selenium__standalone-chrome:4444/wd/hub
  script:
    - mvn test -Dbrowser=chrome -Dheadless=true -Dgrid.url=$SELENIUM_REMOTE_URL
  artifacts:
    when: always
    paths: [target/surefire-reports/, target/screenshots/]
    reports:
      junit: target/surefire-reports/TEST-*.xml
```

## §10 — Parallel Execution

```xml
<!-- testng.xml -->
<suite name="Parallel" parallel="tests" thread-count="4">
  <test name="Chrome Tests">
    <parameter name="browser" value="chrome"/>
    <classes><class name="tests.LoginTest"/><class name="tests.SearchTest"/></classes>
  </test>
  <test name="Firefox Tests">
    <parameter name="browser" value="firefox"/>
    <classes><class name="tests.LoginTest"/><class name="tests.SearchTest"/></classes>
  </test>
</suite>
```

```properties
# JUnit 5 parallel — junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=4
```

## §11 — Advanced Element Interactions

```java
// File Download — configure Chrome download directory
public static ChromeOptions downloadOptions(String downloadDir) {
    ChromeOptions opts = chromeOptions();
    HashMap<String, Object> prefs = new HashMap<>();
    prefs.put("download.default_directory", downloadDir);
    prefs.put("download.prompt_for_download", false);
    prefs.put("plugins.always_open_pdf_externally", true);
    opts.setExperimentalOption("prefs", prefs);
    return opts;
}

// Wait for download to complete
public static File waitForDownload(String downloadDir, String filePattern, int timeoutSec) {
    return new FluentWait<>(new File(downloadDir))
        .withTimeout(Duration.ofSeconds(timeoutSec))
        .pollingEvery(Duration.ofSeconds(1))
        .until(dir -> {
            File[] files = dir.listFiles((d, name) -> name.matches(filePattern)
                && !name.endsWith(".crdownload") && !name.endsWith(".tmp"));
            return (files != null && files.length > 0) ? files[0] : null;
        });
}

// Multiple windows / tabs
public void switchToNewWindow() {
    String originalHandle = driver.getWindowHandle();
    for (String handle : driver.getWindowHandles()) {
        if (!handle.equals(originalHandle)) { driver.switchTo().window(handle); break; }
    }
}

// Network logs (Chrome DevTools Protocol)
public List<LogEntry> getNetworkLogs() {
    return driver.manage().logs().get(LogType.PERFORMANCE).getAll();
}
```

## §12 — Retry Mechanism for Flaky Tests

```java
// TestNG — IRetryAnalyzer
public class RetryAnalyzer implements IRetryAnalyzer {
    private int count = 0;
    private static final int MAX = Config.retryCount();

    @Override
    public boolean retry(ITestResult result) {
        if (count < MAX) { count++; return true; }
        return false;
    }
}
// Usage: @Test(retryAnalyzer = RetryAnalyzer.class)
```

## §13 — Debugging Quick-Reference

| Problem | Cause | Fix |
|---------|-------|-----|
| `StaleElementReferenceException` | DOM re-rendered after find | Re-locate with `wait.until(elementToBeClickable(...))` |
| `NoSuchElementException` | Element not yet in DOM | Use `wait.until(presenceOfElementLocated(...))` |
| `ElementClickInterceptedException` | Overlay/modal covers element | `scrollIntoView()` then click, or JS click |
| `TimeoutException` | Element never appeared | Check locator, increase timeout, verify page loaded |
| `SessionNotCreatedException` | Driver/browser version mismatch | Use WebDriverManager auto-setup |
| `InvalidSelectorException` | Bad CSS/XPath syntax | Validate selector in browser DevTools |
| `UnhandledAlertException` | Unexpected alert/confirm dialog | Add `wait.until(alertIsPresent())` handler |
| `MoveTargetOutOfBoundsException` | Element outside viewport | `scrollIntoView()`
...<truncated>
````


### `reference/python-patterns.md`

````markdown
# Selenium — Python Patterns

## Setup

```bash
pip install selenium pytest
```

## Basic Test

```python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import pytest

class TestLogin:
    def setup_method(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.wait = WebDriverWait(self.driver, 10)

    def test_login(self):
        self.driver.get("https://example.com/login")
        self.wait.until(EC.visibility_of_element_located((By.ID, "username"))).send_keys("user@test.com")
        self.driver.find_element(By.ID, "password").send_keys("password123")
        self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
        self.wait.until(EC.url_contains("/dashboard"))
        assert "Dashboard" in self.driver.title

    def teardown_method(self):
        if self.driver:
            self.driver.quit()
```

## Pytest Fixtures

```python
import pytest
from selenium import webdriver

@pytest.fixture
def driver():
    d = webdriver.Chrome()
    d.maximize_window()
    yield d
    d.quit()

@pytest.fixture
def cloud_driver():
    username = os.environ["LT_USERNAME"]
    access_key = os.environ["LT_ACCESS_KEY"]
    options = webdriver.ChromeOptions()
    lt_options = {
        "platform": "Windows 11",
        "build": "Python Build",
        "name": "Python Test",
        "video": True,
        "network": True,
    }
    options.set_capability("LT:Options", lt_options)
    d = webdriver.Remote(
        command_executor=f"https://{username}:{access_key}@hub.lambdatest.com/wd/hub",
        options=options,
    )
    yield d
    d.quit()
```

## Page Object in Python

```python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class LoginPage:
    USERNAME = (By.ID, "username")
    PASSWORD = (By.ID, "password")
    SUBMIT = (By.CSS_SELECTOR, "button[type='submit']")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def navigate(self):
        self.driver.get("https://example.com/login")
        return self

    def login(self, username, password):
        self.wait.until(EC.visibility_of_element_located(self.USERNAME)).send_keys(username)
        self.driver.find_element(*self.PASSWORD).send_keys(password)
        self.driver.find_element(*self.SUBMIT).click()
        return DashboardPage(self.driver)
```

````


### `reference/ruby-patterns.md`

````markdown
# Selenium — Ruby Patterns

## Setup

```bash
gem install selenium-webdriver rspec
```

## Basic Test (RSpec)

```ruby
require 'selenium-webdriver'
require 'rspec'

RSpec.describe 'Login' do
  before(:each) do
    @driver = Selenium::WebDriver.for :chrome
    @driver.manage.window.maximize
    @wait = Selenium::WebDriver::Wait.new(timeout: 10)
  end

  it 'logs in successfully' do
    @driver.get 'https://example.com/login'
    @wait.until { @driver.find_element(id: 'username') }
    @driver.find_element(id: 'username').send_keys 'user@test.com'
    @driver.find_element(id: 'password').send_keys 'password123'
    @driver.find_element(css: "button[type='submit']").click
    @wait.until { @driver.current_url.include?('/dashboard') }
    expect(@driver.title).to include('Dashboard')
  end

  after(:each) do
    @driver&.quit
  end
end
```

## TestMu AI Cloud (Ruby)

```ruby
caps = Selenium::WebDriver::Remote::Capabilities.new(
  browser_name: 'Chrome',
  browser_version: 'latest',
  'LT:Options': {
    platform: 'Windows 11',
    build: 'Ruby Build',
    name: 'Ruby Test',
    user: ENV['LT_USERNAME'],
    accessKey: ENV['LT_ACCESS_KEY'],
    video: true
  }
)
driver = Selenium::WebDriver.for(
  :remote,
  url: "https://#{ENV['LT_USERNAME']}:#{ENV['LT_ACCESS_KEY']}@hub.lambdatest.com/wd/hub",
  capabilities: caps
)
```

````


### `scripts/scaffold-project.sh`

```
#!/bin/bash
# Scaffold a Selenium + Java project with Maven
set -e

PROJECT_NAME="${1:-selenium-project}"
CLOUD="${2:-}"

if [ -d "$PROJECT_NAME" ]; then
    echo "Error: Directory '$PROJECT_NAME' already exists"
    exit 2
fi

command -v mvn >/dev/null 2>&1 || { echo "Error: Maven (mvn) not found"; exit 1; }

echo "Creating Selenium project: $PROJECT_NAME"
mkdir -p "$PROJECT_NAME/src/main/java/pages"
mkdir -p "$PROJECT_NAME/src/test/java/tests"
mkdir -p "$PROJECT_NAME/src/test/resources"

cat > "$PROJECT_NAME/pom.xml" << 'POMEOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>selenium-tests</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <selenium.version>4.27.0</selenium.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.11.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
POMEOF

echo "✅ Project '$PROJECT_NAME' created"
echo "   cd $PROJECT_NAME && mvn test"
exit 0

```


### `templates/pom.xml`

```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>selenium-tests</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <selenium.version>4.27.0</selenium.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.11.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

```
