🌐 Web Automation Guide / Web Otomasyon Rehberi

This guide explains how to capture Web DOM trees with Playwright, support Shadow DOM / iframes, and generate prioritized locators.

🧭 M6: Live page capture is implemented via PlaywrightLiveExplorer (Microsoft.Playwright .NET SDK) - see Intent-Driven Automation for why this uses Playwright directly instead of the Model Context Protocol.

💡 Select Language / Dil Seçin:


🇬🇧 English Guide

Web Playwright Capture Workflow

  1. Run PlaywrightDomCaptureScript.JavaScript inside browser using Playwright’s page.EvaluateAsync.
  2. Pass the returned DOM JSON to PlaywrightApplicationConnector.ParseJson.
  3. Use the resulting UiElementInfo tree for self-healing or locator generation with PlaywrightLocatorEmitter.

PlaywrightLocatorEmitter expressions are C# source fragments consumed directly by the C# test generator. String values therefore escape quotes, backslashes, and CR/LF/tab characters before emission. A [name='...'] locator applies CSS single-quoted-string escaping first and C# string-literal escaping second, so characters required by the CSS selector survive compilation instead of being consumed by the C# parser. An ID locator follows the browser’s CSS.escape() identifier rules, including leading digits, whitespace, control characters, and CSS punctuation such as # and [, before the resulting selector is escaped for C# source. When no ID, test ID, or name is available, capture emits an ancestor-qualified structural CSS selector using :nth-of-type(...). Such a selector is marked with WebElementInfo.IsStructuralCssSelector and emitted at 0.35 confidence, below the 0.55 confidence used for attribute-based CSS fallback selectors. When the same suggestion is converted to TypeScript, the generator reads the complete C# string literal before re-emitting it, so an escaped quote in an accessible name does not truncate the generated getByRole(..., { name }) locator.

Live Page Exploration

PlaywrightLiveExplorer (AutomationSandbox.PlaywrightLiveExploration) drives a browser, navigates to a URL, and captures a WebElementInfo DOM snapshot directly - no hand-written Playwright test required, and no external Model Context Protocol server:

using PlaywrightLiveExploration;

await using var explorer = await PlaywrightLiveExplorer.LaunchAsync();
WebElementInfo dom = await explorer.CaptureAsync("https://example.test/customers");

This feeds the same WebElementInfo + self-healing pipeline as a manually captured snapshot, so it composes with IntentAutomationPipeline, IntentExplorationBridge, and locator repository recording exactly like CaptureDomSnapshotSomehow() did in earlier examples. See Intent-Driven Automation for why this project uses the Playwright .NET SDK directly rather than a real MCP bridge (which would have required a Node.js-based Playwright MCP server process - a first for this otherwise pure C#/.NET codebase).

Complete Web Automation Example

using System;
using System.Threading.Tasks;
using Microsoft.Playwright;
using WebDiscovery;
using UiModel;
using SelfHealing;

class WebTest
{
    static async Task Main()
    {
        using var playwright = await Playwright.CreateAsync();
        var browser = await playwright.Chromium.LaunchAsync();
        var page = await browser.NewPageAsync();
        await page.GotoAsync("https://example.com/login");

        // 1. Evaluate JavaScript snippet in browser page
        // Wrap in JSON.stringify(...): EvaluateAsync<string> expects the script's result to
        // already be a string, and the capture script itself returns an object.
        string domJson = await page.EvaluateAsync<string>($"() => JSON.stringify(({PlaywrightDomCaptureScript.JavaScript})())");

        // 2. Convert DOM JSON into standard UiElementInfo tree
        UiElementInfo webTree = PlaywrightApplicationConnector.ParseJson(domJson);

        // 3. Generate prioritized locator suggestions for an element
        var targetElement = new WebElementInfo
        {
            TagName = "input",
            Role = "textbox",
            AccessibleName = "Email",
            TestId = "user-email-input",
            Id = "txtEmail"
        };

        var suggestions = PlaywrightLocatorEmitter.Suggest(targetElement);

        Console.WriteLine("Suggested Locators:");
        foreach (var suggestion in suggestions)
        {
            Console.WriteLine($"[{suggestion.Strategy}] ({suggestion.Confidence * 100}% Confidence): {suggestion.Expression}");
        }
    }
}

Iframe Support: Same-Origin vs. Cross-Origin

Web applications frequently embed iframes for isolated widgets, forms, payment gateways, or authentication providers. AutomationSandbox provides distinct handling depending on iframe origin security:

1. Same-Origin Iframes (Automatic Traversal)

When an <iframe> shares the same origin (protocol, domain, and port) as the parent page:

2. Cross-Origin Iframes (Direct Frame Evaluation)

When an <iframe> is hosted on a different origin (e.g. https://checkout.stripe.com, https://accounts.google.com, third-party reCAPTCHA):

using System.Linq;
using System.Text.Json;
using Microsoft.Playwright;
using WebDiscovery;
using UiModel;
using SelfHealing;

// 1. Locate the cross-origin frame via Playwright
IFrame? paymentFrame = page.Frames.FirstOrDefault(f => f.Url.Contains("checkout.stripe.com"))
    ?? page.FrameByName("stripe-frame");

if (paymentFrame != null)
{
    // 2. Evaluate capture script inside the frame context directly
    string frameDomJson = await paymentFrame.EvaluateAsync<string>(
        $"() => JSON.stringify(({PlaywrightDomCaptureScript.JavaScript})())");

    // 3. Deserialize and map to UiElementInfo tree
    var frameDom = JsonSerializer.Deserialize<WebElementInfo>(
        frameDomJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
    UiElementInfo frameTree = WebElementMapper.ToUiElementTree(frameDom!);

    // 4. Resolve / heal target element inside the cross-origin frame
    var healResult = SelfHealingResolver.Resolve(expectedCardInputSnapshot, frameTree);

    // 5. In test scripts, target cross-origin elements via FrameLocator or frame instance:
    // C#: await Page.FrameLocator("iframe[src*='checkout.stripe.com']").GetByTestId("card-number").FillAsync("4242...");
    // TS: await page.frameLocator('iframe[src*=\'checkout.stripe.com\']').getByTestId('card-number').fill('4242...');
}

🇹🇷 Türkçe Kılavuz

Web Playwright Tarama Akışı

  1. Playwright’ın page.EvaluateAsync fonksiyonu ile PlaywrightDomCaptureScript.JavaScript kodunu tarayıcıda çalıştırın.
  2. Dönen DOM JSON verisini PlaywrightApplicationConnector.ParseJson fonksiyonuna verin.
  3. Oluşan standart UiElementInfo ağacını iyileştirme motoruna verin veya PlaywrightLocatorEmitter ile önerilen Playwright kodlarını alın.

PlaywrightLocatorEmitter ifadeleri C# test üreticisinin doğrudan kullandığı C# kaynak parçalarıdır. Bu nedenle tırnak, ters eğik çizgi ve CR/LF/tab karakterleri üretilmeden önce kaçırılır. [name='...'] locator’ında önce CSS tek-tırnaklı string kaçışı, ardından C# string literal kaçışı uygulanır; böylece CSS seçicisinin gerektirdiği karakterler C# parser tarafından tüketilmeden derlenmiş koda ulaşır. ID locator’ı; baştaki rakamlar, boşluklar, kontrol karakterleri ile # ve [ gibi CSS noktalama işaretleri dahil olmak üzere tarayıcının CSS.escape() identifier kurallarını uygular, ardından oluşan seçiciyi C# kaynak kodu için kaçırır. ID, test ID veya name olmadığında tarama; :nth-of-type(...) kullanan, üst öğelerle nitelenmiş yapısal bir CSS seçicisi üretir. Bu seçici WebElementInfo.IsStructuralCssSelector ile işaretlenir ve öznitelik tabanlı CSS fallback seçicilerinin 0.55 değerinden düşük olan 0.35 güvenle yayımlanır. Aynı öneri TypeScript’e dönüştürülürken üretici C# string literal’ının tamamını okur; bu sayede erişilebilir addaki kaçırılmış bir çift tırnak, üretilen getByRole(..., { name }) locator’ını yarıda kesmez.

Canlı Sayfa Keşfi

PlaywrightLiveExplorer (AutomationSandbox.PlaywrightLiveExploration) bir tarayıcıyı yönetip verilen URL’ye gider ve doğrudan bir WebElementInfo DOM snapshot’ı yakalar - elle yazılmış bir Playwright testine ya da harici bir Model Context Protocol sunucusuna gerek kalmadan:

using PlaywrightLiveExploration;

await using var explorer = await PlaywrightLiveExplorer.LaunchAsync();
WebElementInfo dom = await explorer.CaptureAsync("https://example.test/customers");

Bu, elle yakalanmış bir snapshot ile aynı WebElementInfo + self-healing akışını besler; IntentAutomationPipeline, IntentExplorationBridge ve locator repository kaydı ile aynen uyumludur. Bu projenin neden gerçek bir MCP köprüsü yerine (bu, saf C#/.NET kod tabanına ilk kez bir Node.js tabanlı Playwright MCP sunucu süreci gerektirirdi) doğrudan Playwright .NET SDK’sını kullandığına dair gerekçe için Intent Tabanlı Otomasyon sayfasına bakın.

Tam C# Web Otomasyon Örneği

using System;
using System.Threading.Tasks;
using Microsoft.Playwright;
using WebDiscovery;
using UiModel;
using SelfHealing;

class WebTest
{
    static async Task Main()
    {
        using var playwright = await Playwright.CreateAsync();
        var browser = await playwright.Chromium.LaunchAsync();
        var page = await browser.NewPageAsync();
        await page.GotoAsync("https://example.com/login");

        // 1. Evaluate JavaScript snippet in browser page
        // Wrap in JSON.stringify(...): EvaluateAsync<string> expects the script's result to
        // already be a string, and the capture script itself returns an object.
        string domJson = await page.EvaluateAsync<string>($"() => JSON.stringify(({PlaywrightDomCaptureScript.JavaScript})())");

        // 2. Convert DOM JSON into standard UiElementInfo tree
        UiElementInfo webTree = PlaywrightApplicationConnector.ParseJson(domJson);

        // 3. Generate prioritized locator suggestions for an element
        var targetElement = new WebElementInfo
        {
            TagName = "input",
            Role = "textbox",
            AccessibleName = "Email",
            TestId = "user-email-input",
            Id = "txtEmail"
        };

        var suggestions = PlaywrightLocatorEmitter.Suggest(targetElement);

        Console.WriteLine("Suggested Locators:");
        foreach (var suggestion in suggestions)
        {
            Console.WriteLine($"[{suggestion.Strategy}] ({suggestion.Confidence * 100}% Confidence): {suggestion.Expression}");
        }
    }
}

Iframe Desteği: Same-Origin ve Cross-Origin

Web uygulamaları bağımsız bileşenler, formlar, ödeme sistemleri veya kimlik doğrulama sağlayıcıları için sıklıkla iframe kullanır. AutomationSandbox, iframe’in origin güvenliğine göre iki farklı yaklaşım sunar:

1. Same-Origin Iframe’ler (Otomatik Ağaç Gezintisi)

Bir <iframe> ana sayfa ile aynı origin’i (protokol, alan adı, port) paylaştığında:

2. Cross-Origin Iframe’ler (Doğrudan Frame Değerlendirmesi)

Bir <iframe> farklı bir origin’den yüklendiğinde (örn. https://checkout.stripe.com, https://accounts.google.com, üçüncü parti reCAPTCHA):

using System.Linq;
using System.Text.Json;
using Microsoft.Playwright;
using WebDiscovery;
using UiModel;
using SelfHealing;

// 1. Playwright üzerinden cross-origin frame'i bulun
IFrame? paymentFrame = page.Frames.FirstOrDefault(f => f.Url.Contains("checkout.stripe.com"))
    ?? page.FrameByName("stripe-frame");

if (paymentFrame != null)
{
    // 2. Tarama betiğini doğrudan frame bağlamında çalıştırın
    string frameDomJson = await paymentFrame.EvaluateAsync<string>(
        $"() => JSON.stringify(({PlaywrightDomCaptureScript.JavaScript})())");

    // 3. JSON'ı deserialize edip UiElementInfo ağacına dönüştürün
    var frameDom = JsonSerializer.Deserialize<WebElementInfo>(
        frameDomJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
    UiElementInfo frameTree = WebElementMapper.ToUiElementTree(frameDom!);

    // 4. Cross-origin frame içerisindeki hedef elemanı iyileştirin / bulun
    var healResult = SelfHealingResolver.Resolve(expectedCardInputSnapshot, frameTree);

    // 5. Test kodlarında FrameLocator veya frame nesnesi ile hedefleyin:
    // C#: await Page.FrameLocator("iframe[src*='checkout.stripe.com']").GetByTestId("card-number").FillAsync("4242...");
    // TS: await page.frameLocator('iframe[src*=\'checkout.stripe.com\']').getByTestId('card-number').fill('4242...');
}