This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Thursday, 27 November 2025
Modern E2E (End-To-End, käyttäen sivustoasi kuten käyttäjät haluaisivat) -testauksen ei tarvitse olla tuskallista. Tämä kattava opas näyttää, miten PuppeteerSharp -mallia käytetään nopeaan ja luotettavaan selainautomaatioon .NET-verkossa, joka kattaa kaiken perustestauksesta pdf-sukupolveen ja verkkokaapimiseen. Microsoftin Playwright on modernimpi monikirjainratkaisu, mutta valitsin PuppeteerSharpin tähän blogiin, koska se on se, minkä tiesin ja Chrome-testi vain riitti tarpeisiini. Jos tarvitset Firefox- ja Safari-tukea, tarkista, mitä tarvitsen. Näytelmäkirjailijan opas Sen sijaan.
Jos olet koskaan työskennellyt Seleeni Lopputestauksesta tiedät, että se voi olla oikea kipu takaraivossa. Välillä painitaan kuljettajaversioiden kanssa, käsitellään hilseileviä testejä, jotka toimivat koneella, mutta eivät missään muualla, ja WebDriverin protokollan yleinen hitaus saa sinut haluamaan heittää kaiken sisään ja testata käsin.
Syötä PuppeteerSharp - Googlen .net-portti Puppeteer Kirjasto. Se on kuin Seleniumin nuorempi, nopeampi serkku, joka vaivautuu tulemaan ajoissa eikä vaadi lataamaan seitsemäätoista eri selainajuria.
Tässä artikkelissa kerron, miten olen toteuttanut PuppeteerSharp for E2E -testauksen juuri tässä blogissa, täydennettynä repo-ohjelman todellisilla koodiesimerkeillä. Käsittelemme testausta, PDF-sukupolvea, verkkokaappausta ja vertaamme sitä vaihtoehtoihin.
PuppeteerSharp on .NET-kirjasto, joka tarjoaa korkean tason API:n Chrome- tai Chrome-selainten hallitsemiseksi Chrome DevTools -protokollaToisin kuin Seleeni, jossa käytetään WebDriver-protokolla PuppeteerSharp puhuu suoraan selaimelle DevToolsin kautta.
Ajattele asiaa näin:
graph LR
A[Test Code] -->|WebDriver Protocol| B[Selenium]
B -->|JSON Wire Protocol| C[Browser Driver]
C -->|Commands| D[Browser]
E[Test Code] -->|DevTools Protocol| F[PuppeteerSharp]
F -->|Direct Connection| G[Chrome/Chromium]
style A stroke:#333,stroke-width:2px
style E stroke:#333,stroke-width:2px
style F stroke:#0066cc,stroke-width:3px
style G stroke:#0066cc,stroke-width:3px
Ennen kuin sukeltamme syvemmälle, puhutaan siitä, missä kohtaa E2E-testit sopivat suureen kokonaisuuteen. Olet luultavasti kuullut testipyramidista - näin se käytännössä toimii:
graph TB
subgraph "Testing Pyramid"
E2E[E2E Tests<br/>Few, Slow, High Confidence<br/>Test full user journeys]
INT[Integration Tests<br/>Medium number, Medium speed<br/>Test component interactions]
UNIT[Unit Tests<br/>Many, Fast, Low Cost<br/>Test individual functions]
end
subgraph "Trade-offs"
SPEED[Speed]
CONF[Confidence]
COST[Cost]
end
subgraph "When to Use E2E"
W1[Critical user journeys<br/>e.g. checkout, login]
W2[Cross-browser compatibility]
W3[JavaScript-heavy UIs]
W4[Complex user interactions]
end
E2E -.->|Slow but high confidence| CONF
INT -.->|Balanced| SPEED
UNIT -.->|Fast and cheap| SPEED
E2E -.->|Expensive to run| COST
UNIT -.->|Cheap to run| COST
style E2E stroke:#cc0000,stroke-width:3px
style INT stroke:#ff9900,stroke-width:2px
style UNIT stroke:#00aa00,stroke-width:2px
style CONF stroke:#0066cc,stroke-width:2px
style SPEED stroke:#00aa00,stroke-width:2px
style COST stroke:#cc0000,stroke-width:2px
Todellisuustarkistus:
Kun tarvitset E2E-testejä:
Kun et tarvitse E2E-testejä:
Anna kun lasken tapoja:
Ei kuljettajien hallintafaffiaPuppeteerSharp lataa ja hallinnoi Chrome-selainta puolestasi. ChromeDriverin versiot eivät enää vedä vertoja asennetulle Chrome-versiolle.
Nopeampi suoritusDevTools Protokolla on huomattavasti nopeampi kuin WebDriver. Testisi sujuvat nopeammin, ja odotat asioita vähemmän.
Parempi API: API on modernimpi ja intuitiivismpi. Se on async/aodotus koko matkan alaspäin, mikä sopii kauniisti modernin .NET-kehityksen kanssa.
Sisäänrakennettu kuvakaappaus ja PDF-sukupolviHaluatko kuvakaappauksen, kun testi epäonnistuu? PuppeteerSharp on yksinkertainen.
Verkkopyynnöt: Voit siepata, muokata tai estää verkkopyyntöjä helposti – loistavasti offline-skenaarioiden testaamiseen tai API-vastausten pilkkaamiseen.
Oikea javascript-teloitus: Suorita JavaScript sivukontekstissa ja hanki tulokset takaisin tavalla, joka ei saa sinua itkemään.
Ensinnäkin: PuppeteerSharp NuGet-paketti:
dotnet add package PuppeteerSharp
Tässä on testiprojektin kokoonpanoni (Mostlylucid.Test/Mostlylucid.Test.csproj:23):
<PackageReference Include="PuppeteerSharp" Version="20.2.4" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Käytän xUnit (ASP.NET Coren oletus), mutta PuppeteerSharp toimii yhtä hyvin NUNIT tai MSTest.
Sen sijaan, että olisin toistanut asetus- ja kaatokoodia jokaisessa testissä, olen luonut perusluokan (Mostlylucid.Test/E2E/E2ETestBase.cs:12), joka käsittelee selaimen elinkaaren hallintaa:
using PuppeteerSharp;
using Xunit.Abstractions;
namespace Mostlylucid.Test.E2E;
public abstract class E2ETestBase : IAsyncLifetime
{
protected readonly ITestOutputHelper Output;
protected IBrowser Browser = null!;
protected IPage Page = null!;
protected const string BaseUrl = "http://localhost:8080";
protected const int DefaultTimeout = 30000;
protected E2ETestBase(ITestOutputHelper output)
{
Output = output;
}
Toteutamme I AsyncLifetime XUnitista, joka tarjoaa async-asetukset/teroitukset. Toisin kuin perinteiset rakentajat, tämä antaa meidän odottaa kunnolla selaimen alustusta.
public async Task InitializeAsync()
{
// Download Chromium on first run
var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
// Launch browser with sensible defaults
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true, // Set false for debugging
DefaultViewport = new ViewPortOptions
{
Width = 1400,
Height = 900
},
Args = new[]
{
"--no-sandbox",
"--disable-setuid-sandbox"
}
});
Page = await Browser.NewPageAsync();
Page.DefaultTimeout = DefaultTimeout;
}
Erytropoietiini BrowserFetcher Lataa automaattisesti yhteensopivan Chromium-version ensimmäisellä kierroksella - manuaalista ohjauksen hallintaa ei tarvita. --no-sandbox Docker/CI-ympäristöihin vaaditaan liput.
public async Task DisposeAsync()
{
if (Page != null) await Page.CloseAsync();
if (Browser != null) await Browser.CloseAsync();
}
}
Oikea hävittäminen on tärkeää muistivuotojen välttämiseksi. Jokainen selain-installaatio käyttää 100-200MB RAM-muistia.
Perusluokkaan kuuluu apukeinot kattilalevyn vähentämiseksi (Mostlylucid.Test/E2E/E2ETestBase.cs:72-172):
// Navigation with automatic network idle waiting
protected async Task NavigateAsync(string path)
{
var url = path.StartsWith("http") ? path : $"{BaseUrl}{path}";
await Page.GoToAsync(url, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle2 }
});
}
// Safe element waiting with timeout handling
protected async Task<IElementHandle?> WaitForSelectorAsync(string selector, int timeout = 5000)
{
try
{
return await Page.WaitForSelectorAsync(selector, new WaitForSelectorOptions
{
Timeout = timeout,
Visible = true
});
}
catch (WaitTaskTimeoutException)
{
return null; // Graceful degradation
}
}
// Common element operations
protected async Task<bool> ElementExistsAsync(string selector) =>
await Page.QuerySelectorAsync(selector) != null;
protected async Task<string?> GetTextContentAsync(string selector)
{
var element = await Page.QuerySelectorAsync(selector);
return element == null ? null :
await Page.EvaluateFunctionAsync<string>("el => el.textContent", element);
}
protected async Task TypeAsync(string selector, string text, int delay = 50)
{
await Page.WaitForSelectorAsync(selector);
await Page.TypeAsync(selector, text, new TypeOptions { Delay = delay });
}
protected async Task ClickAsync(string selector)
{
await Page.WaitForSelectorAsync(selector);
await Page.ClickAsync(selector);
}
Nämä käsittelevät tylsiä kappaleita - odottavat alkuaineiden olemassaoloa, viehkeää aikakatkaisun käsittelyä ja automaattista kirjautumista, kun testit epäonnistuvat CI:ssä.
Siirrytään hyviin juttuihin - todellisten testien kirjoittamiseen. Tässä todellinen testi blogini suodatinpalkkitoiminnosta (Mostlylucid.Test/E2E/FilterBarTests.cs:20-50):
[Fact(Skip = "Local E2E test - requires site to be running on localhost:8080")]
public async Task FilterBar_LanguageDropdown_ShowsLanguages()
{
// Arrange
await NavigateAsync("/blog");
// Act - Click the language dropdown button
var dropdownButton = await WaitForSelectorAsync("#LanguageDropDown button");
Assert.NotNull(dropdownButton);
await ClickAsync("#LanguageDropDown button");
await WaitAsync(300);
// Assert - Dropdown menu should be visible with language options
var dropdownOpen = await EvaluateFunctionAsync<bool>(@"() => {
const dropdown = document.querySelector('#LanguageDropDown div[x-show]');
if (!dropdown) return false;
const style = window.getComputedStyle(dropdown);
return style.display !== 'none';
}");
Assert.True(dropdownOpen, "Language dropdown should be open");
// Check that English option exists
var hasEnglish = await EvaluateFunctionAsync<bool>(@"() => {
const options = document.querySelectorAll('#LanguageDropDown li a');
return Array.from(options).some(opt => opt.textContent.toLowerCase().includes('english'));
}");
Assert.True(hasEnglish, "Language dropdown should contain English option");
Output.WriteLine("✅ Language dropdown shows languages correctly");
}
Tässä kokeessa tarkistetaan, että kielenpudotukseni toimii oikein. Katsotaan, mikä saa sen toimimaan:
[Fact(Skip = "Local E2E test - requires site to be running on localhost:8080")]
Olen jättänyt tämän testin väliin oletuksena, koska se edellyttää, että sivusto on toiminnassa paikallisesti. E2E-testeissä haluat yleensä ajaa ne tilauksesta eikä joka rakenteella. Voit irrottaa ne, kun olet valmis ajamaan ne, tai tehdä ne erillisessä Cl-tehtävässä, jossa olet saanut sivuston pyörimään.
var dropdownOpen = await EvaluateFunctionAsync<bool>(@"() => {
const dropdown = document.querySelector('#LanguageDropDown div[x-show]');
if (!dropdown) return false;
const style = window.getComputedStyle(dropdown);
return style.display !== 'none';
}");
Tämä on yksi niistä alueista, joilla PuppeteerSharp ehdottomasti loistaa. EvaluateFunctionAsync Menetelmän avulla voit ajaa JavaScriptin selainkontekstissa ja saada tuloksen takaisin oikeana .NET-tyyppisenä. Tässä tapauksessa tarkistan, näkyykö pudotuspeli (ei vain esiintyä DOM:ssa) katsomalla sen laskelmia tyylejä.
Vertaa tätä Seleniumiin, jossa sinun pitäisi:
Blogini käyttää HTMX Laajamittaisesti (server-side renderointi ilman kirjoitusta JavaScript). Tässä testi, jossa tarkistetaan lajittelutoiminnot (Mostlylucid.Test/E2E/FilterBarTests.cs:98-126):
[Fact(Skip = "Local E2E test - requires site to be running on localhost:8080")]
public async Task FilterBar_SortOrder_ChangesPostOrder()
{
// Arrange
await NavigateAsync("/blog");
// Get the first post title before sorting
var firstPostBefore = await EvaluateFunctionAsync<string>(@"() => {
const postLink = document.querySelector('.post-title, article h2 a, #contentcontainer article a');
return postLink?.textContent?.trim() || '';
}");
Output.WriteLine($"First post before sort: {firstPostBefore}");
// Act - Change sort order to "Oldest first"
await Page.SelectAsync("#orderSelect", "date_asc");
await WaitAsync(1000); // Wait for HTMX to update
// Assert - Post order should have changed
var firstPostAfter = await EvaluateFunctionAsync<string>(@"() => {
const postLink = document.querySelector('.post-title, article h2 a, #contentcontainer article a');
return postLink?.textContent?.trim() || '';
}");
Output.WriteLine($"First post after sort: {firstPostAfter}");
var selectValue = await EvaluateFunctionAsync<string>("() => document.querySelector('#orderSelect')?.value");
Assert.Equal("date_asc", selectValue);
Output.WriteLine("✅ Sort order selection works correctly");
}
Avain tässä on await WaitAsync(1000) Kun valittu arvo on vaihdettu. HTMX tarvitsee hetken aikaa esittääkseen pyyntönsä ja päivittääkseen DOM:n. Täydellisessä maailmassa odottaisimme tietyn verkkopyynnön valmistumista, mutta yksinkertaisissa tapauksissa lyhyt viive on hyvä.
Tässä on röyhkeä testi, joka tarkistaa, että suodatinpalkkini on kunnolla piilotettu mobiililaitteisiin (Mostlylucid.Test/E2E/FilterBarTests.cs:216-245):
[Fact(Skip = "Local E2E test - requires site to be running on localhost:8080")]
public async Task FilterBar_ResponsiveDesign_HiddenOnMobile()
{
// Arrange - Set mobile viewport
await Page.SetViewportAsync(new ViewPortOptions
{
Width = 375,
Height = 667
});
await NavigateAsync("/blog");
await WaitAsync(500);
// Assert - Filter bar should be hidden on mobile
var filterBarVisible = await EvaluateFunctionAsync<bool>(@"() => {
const filterBar = document.querySelector('.hidden.lg\\:flex');
if (!filterBar) return true;
const rect = filterBar.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}");
Assert.False(filterBarVisible, "Filter bar should be hidden on mobile viewport");
Output.WriteLine("✅ Filter bar correctly hidden on mobile");
// Reset viewport
await Page.SetViewportAsync(new ViewPortOptions
{
Width = 1400,
Height = 900
});
}
Voit muuttaa näkymää milloin tahansa, mikä on loistavaa reagoivien asettelujen testaamiseen. Paljon helpompaa kuin selainikkunan uudelleenlaajentaminen manuaalisesti!
Yksi suosikkiominaisuuksistani on kyky siepata ja muokata verkkopyyntöjä. Tämä on korvaamaton keino testata virhetilanteita tai offline-skenaarioita:
await Page.SetRequestInterceptionAsync(true);
Page.Request += async (sender, e) =>
{
// Block all image requests to speed up tests
if (e.Request.ResourceType == ResourceType.Image)
{
await e.Request.AbortAsync();
}
// Mock API responses
else if (e.Request.Url.Contains("/api/posts"))
{
await e.Request.RespondAsync(new ResponseData
{
Status = HttpStatusCode.OK,
ContentType = "application/json",
Body = "{\"posts\": []}"
});
}
else
{
await e.Request.ContinueAsync();
}
};
Kun testi epäonnistuu, kuvakaappauksen arvo on tuhat lokiviestiä:
try
{
// Your test code here
await Page.ClickAsync("#someButton");
}
catch (Exception)
{
// Take a screenshot on failure
await Page.ScreenshotAsync("test-failure.png");
throw; // Re-throw to fail the test
}
Voit jopa luoda PDF-sivuja, joista on hyötyä palvelimen renderöinnin tai tyylisivujen testauksessa:
await Page.PdfAsync("page.pdf", new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true
});
PuppeteerSharp voi jopa kerätä JavaScript-koodin peittotietoja:
await Page.Coverage.StartJSCoverageAsync();
await Page.GoToAsync("http://localhost:8080");
var coverage = await Page.Coverage.StopJSCoverageAsync();
var totalBytes = coverage.Sum(c => c.Text.Length);
var usedBytes = coverage.Sum(c => c.Ranges.Sum(r => r.End - r.Start));
var percentUsed = usedBytes / (double)totalBytes * 100;
Output.WriteLine($"JavaScript coverage: {percentUsed:F2}%");
Katsotaanpa kunnolla, kuinka PuppeteerSharp pinoutuu muihin E2E-testaustyökaluihin:
graph TD
A[E2E Testing Tools] --> B[Selenium WebDriver]
A --> C[PuppeteerSharp]
A --> D[Playwright]
A --> E[Cypress]
B --> B1[❌ Slow WebDriver protocol]
B --> B2[❌ Driver management hassle]
B --> B3[✅ Multi-browser support]
B --> B4[✅ Mature ecosystem]
C --> C1[✅ Fast DevTools protocol]
C --> C2[✅ Auto browser management]
C --> C3[❌ Chrome/Chromium only]
C --> C4[✅ Great .NET integration]
D --> D1[✅ Fast DevTools protocol]
D --> D2[✅ Auto browser management]
D --> D3[✅ Multi-browser support]
D --> D4[⚠️ Newer to .NET ecosystem]
E --> E1[✅ Great developer experience]
E --> E2[❌ JavaScript only]
E --> E3[❌ Not for .NET]
E --> E4[✅ Excellent documentation]
style C stroke:#0066cc,stroke-width:3px
style C1 stroke:#00aa00,stroke-width:2px
style C2 stroke:#00aa00,stroke-width:2px
style C4 stroke:#00aa00,stroke-width:2px
Vanha kaarti
Seleniumia on ollut olemassa vuodesta 2004 lähtien, ja se näkyy. Se on kypsä, hyvin dokumentoitu ja tukee jokaista auringon alla olevaa selainta. Mutta se näyttää myös ikänsä:
Plussat:
Miinukset:
Milloin sitä käytetään: Kun sinun täytyy ehdottomasti testata useita selaimia, tai kun olet jo investoinut Selenium-ekosysteemiin.
Uusi poika korttelissa
Näytelmäkirjailija on Microsoftin vastaus Puppeteerille, .NET-tuki Se on pääasiassa PuppeteerSharp, mutta moniselitteisellä tuella:
Plussat:
Miinukset:
Milloin sitä käytetään: Kun tarvitset multi-selaustukea, mutta haluat modernin API:n. Jos olet aloittamassa uutta projektia ja tarvitset ristiselostustestejä, Playwright on todennäköisesti paras vaihtoehtosi.
JavaScript-kehittäjän kulta
Cypress on loistava, jos olet töissä JavaScript/TypeScriptissä, mutta se ei käynnisty .NET-kehittäjille:
Plussat:
Miinukset:
Milloin sitä käytetään: Älä, kirjoitat .net-koodia.
Tässä osuuteni:
graph TD
A[What E2E tool?] --> B{Need multi-browser testing?}
B -->|Yes| C{Starting new project?}
B -->|No| D[PuppeteerSharp]
C -->|Yes| E[Playwright]
C -->|No| F{Invested in Selenium?}
F -->|Yes| G[Stick with Selenium]
F -->|No| E
D --> H[✅ Fast, simple, reliable]
E --> I[✅ Modern, flexible]
G --> J[⚠️ Consider migrating]
style D stroke:#0066cc,stroke-width:3px
style H stroke:#00aa00,stroke-width:2px
Useimmille .NET-kehittäjille, jotka rakentavat nykyaikaisia verkkosovelluksia:
E2E-testit ovat hyviä paikallisessa koneessa, mutta niiden pitää toimia myös CI/CD-putkistoissa. GitHub-toimet:
name: E2E Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.0.x'
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Start application
run: |
dotnet run --project Mostlylucid/Mostlylucid.csproj &
echo $! > app.pid
- name: Wait for application to start
run: |
timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done'
- name: Run E2E tests
run: |
dotnet test Mostlylucid.Test/Mostlylucid.Test.csproj \
--filter "Category=E2E" \
--logger "console;verbosity=detailed"
- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v3
with:
name: test-screenshots
path: '**/test-failure-*.png'
- name: Stop application
if: always()
run: |
kill $(cat app.pid) || true
Avainkohdat:
E2E-testit voivat olla epäluotettavia - ne menevät joskus ohi ja epäonnistuvat toisissa. Yleensä kyse on ajoituksesta. Näin vältät ne:
Huono:
await Page.ClickAsync("#button");
var text = await GetTextContentAsync("#result");
Assert.Equal("Success", text);
Hyvä:
await Page.ClickAsync("#button");
await Page.WaitForSelectorAsync("#result");
var text = await GetTextContentAsync("#result");
Assert.Equal("Success", text);
Odota aina, että elementti, jonka kanssa olet vuorovaikutuksessa, on olemassa ja näkyvä.
Jokaisen testin tulisi olla täysin riippumaton. Älä luota aiempien testien tilaan:
Huono:
[Fact]
public async Task Test1_Login()
{
await LoginAsync("user", "password");
// User is now logged in for subsequent tests
}
[Fact]
public async Task Test2_ViewDashboard()
{
// Assumes user is still logged in from Test1
await NavigateAsync("/dashboard");
}
Hyvä:
[Fact]
public async Task Test1_Login()
{
await LoginAsync("user", "password");
await LogoutAsync(); // Clean up
}
[Fact]
public async Task Test2_ViewDashboard()
{
await LoginAsync("user", "password"); // Set up needed state
await NavigateAsync("/dashboard");
await LogoutAsync(); // Clean up
}
Monimutkaisille sivuille, käytä Page Object -mallia pitääksesi testisi kunnossa:
public class BlogPageObject
{
private readonly IPage _page;
public BlogPageObject(IPage page)
{
_page = page;
}
public async Task SelectLanguageAsync(string language)
{
await _page.ClickAsync("#LanguageDropDown button");
await _page.WaitAsync(300);
await _page.ClickAsync($"#LanguageDropDown a:has-text('{language}')");
}
public async Task<string[]> GetPostTitlesAsync()
{
return await _page.EvaluateFunctionAsync<string[]>(@"() => {
return Array.from(document.querySelectorAll('.post-title'))
.map(el => el.textContent.trim());
}");
}
}
// Usage in tests
[Fact]
public async Task Can_Filter_By_Language()
{
var blogPage = new BlogPageObject(Page);
await NavigateAsync("/blog");
await blogPage.SelectLanguageAsync("Spanish");
var titles = await blogPage.GetPostTitlesAsync();
Assert.All(titles, title => Assert.NotEmpty(title));
}
E2E-testit ovat hitaampia kuin yksikkötestit, niitä ei voi kiertää, mutta niistä voi tehdä nopeampia:
XUnit tekee oletuksena rinnakkaistestejä, mutta jaetun tilan suhteen on oltava varovainen:
[Collection("E2E Tests")] // Tests in same collection run sequentially
public class FilterBarTests : E2ETestBase
{
// Tests here share resources
}
[Collection("Blog Tests")] // Different collection runs in parallel
public class BlogTests : E2ETestBase
{
// Tests here run in parallel with FilterBarTests
}
Nopeuta testejä poistamalla ominaisuuksia, joita et tarvitse:
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[]
{
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage", // Overcome limited resource problems
"--disable-accelerated-2d-canvas",
"--disable-gpu", // Not needed for headless
"--disable-images", // Don't load images if you don't need them
"--disable-javascript", // Only if testing static content
}
});
Vähennä tarpeettomia resursseja asioiden nopeuttamiseksi:
await Page.SetRequestInterceptionAsync(true);
Page.Request += async (sender, e) =>
{
var blockedResourceTypes = new[]
{
ResourceType.Image,
ResourceType.Media,
ResourceType.Font,
ResourceType.StyleSheet // If you don't need to test styling
};
if (blockedResourceTypes.Contains(e.Request.ResourceType))
{
await e.Request.AbortAsync();
}
else
{
await e.Request.ContinueAsync();
}
};
Kun testit epäonnistuvat (ja ne epäonnistuvat), sinun on debuggattava ne. Tässä muutamia tekniikoita:
Aseta Headless = false Katsoaksesi selainta toiminnassa:
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = false,
SlowMo = 100, // Slow down by 100ms to see what's happening
});
Voit avata DevToolsin ohjelmallisesti:
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = false,
Devtools = true, // Auto-open DevTools
});
Kaappaa konsoliviestit selaimesta:
Page.Console += (sender, e) =>
{
Output.WriteLine($"Browser console: {e.Message.Text}");
};
Kirjaa kaikki verkkopyynnöt:
Page.Request += (sender, e) =>
{
Output.WriteLine($"Request: {e.Request.Method} {e.Request.Url}");
};
Page.Response += (sender, e) =>
{
Output.WriteLine($"Response: {e.Response.Status} {e.Response.Url}");
};
Tässä muutamia malleja, joita käytän säännöllisesti E2E-testeissäni:
[Fact]
public async Task Can_Submit_Comment()
{
await NavigateAsync("/blog/some-post");
// Fill in the comment form
await TypeAsync("#comment-name", "Test User");
await TypeAsync("#comment-email", "[email protected]");
await TypeAsync("#comment-content", "This is a test comment");
// Submit the form
await ClickAsync("#comment-submit");
// Wait for success message
await WaitForSelectorAsync(".comment-success");
// Verify the comment appears
var commentText = await GetTextContentAsync(".comment-list .comment:last-child .comment-content");
Assert.Contains("test comment", commentText.ToLower());
}
[Fact]
public async Task Can_Navigate_With_Keyboard()
{
await NavigateAsync("/blog");
// Focus the search box
await Page.FocusAsync("#search");
// Type a search query
await Page.Keyboard.TypeAsync("testing");
// Press arrow down to select first result
await Page.Keyboard.PressAsync("ArrowDown");
// Press enter to navigate
await Page.Keyboard.PressAsync("Enter");
// Verify we navigated to the right page
await WaitAsync(1000);
Assert.Contains("/blog/", Page.Url);
}
[Fact]
public async Task Can_Upload_Image()
{
await NavigateAsync("/admin/upload");
// Create a test file
var testFilePath = Path.Combine(Path.GetTempPath(), "test-image.jpg");
File.WriteAllBytes(testFilePath, new byte[] { 0xFF, 0xD8, 0xFF }); // JPEG header
// Upload the file
var fileInput = await Page.QuerySelectorAsync("input[type=file]");
await fileInput.UploadFileAsync(testFilePath);
await ClickAsync("#upload-submit");
// Verify upload succeeded
await WaitForSelectorAsync(".upload-success");
// Clean up
File.Delete(testFilePath);
}
[Fact]
public async Task Can_teAsync("/admin/posts");
var dragSource = await Page.QuerySelectorAsync(".post-item[data-id='1']");
var dropTarget = await Page.QuerySelectorAsync(".post-item[data-id='3']");
var sourceBox = await dragSource.BoundingBoxAsync();
var targetBox = await dropTarget.BoundingBoxAsync();
// Perform drag and drop
await Page.Mouse.MoveAsync(sourceBox.X + sourceBox.Width / 2, sourceBox.Y + sourceBox.Height / 2);
await Page.Mouse.DownAsync();
await Page.Mouse.MoveAsync(targetBox.X + targetBox.Width / 2, targetBox.Y + targetBox.Height / 2);
await Page.Mouse.UpAsync();
await WaitAsync(500);
// Verify new order
var firstItemId = await Page.EvaluateFunctionAsync<string>(
"() => document.querySelector('.post-item').dataset.id"
);
Assert.Equal("1", firstItemId);
}
Voit integroida PuppeteerSharpin ASP.NET Coren Web ApplicationFactory Integroidumpi testauskokemus:
public class E2EWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseUrls("http://localhost:5050");
builder.ConfigureServices(services =>
{
// Override services for testing
// For example, use in-memory database
services.RemoveAll<DbContextOptions<MostlylucidDbContext>>();
services.AddDbContext<MostlylucidDbContext>(options =>
{
options.UseInMemoryDatabase("TestDb");
});
});
}
}
public abstract class IntegratedE2ETestBase : E2ETestBase, IClassFixture<E2EWebApplicationFactory>
{
protected E2EWebApplicationFactory Factory { get; }
protected IntegratedE2ETestBase(E2EWebApplicationFactory factory, ITestOutputHelper output)
: base(output)
{
Factory = factory;
}
public override async Task InitializeAsync()
{
await base.InitializeAsync();
// Application is automatically started by WebApplicationFactory
// Override BaseUrl to use the factory's address
BaseUrl = "http://localhost:5050";
}
}
Vaikka E2E-testit ovat nerokkaita, PuppeteerSharp on Sveitsin armeijan veitsi, joka voi tehdä paljon enemmän. Yksi sen suosituimmista käyttökohteista on pdf:ien tuottaminen web-sisällöstä - se on uskomattoman hyödyllinen tähän, vaikkakaan ei ilman gotcha-tietoja. Jos rakennat laskuja, raportteja tai minkä tahansa asiakirjasukupolven järjestelmän, tämä osio säästää sinulta tunteja vianetsintää.
Idea on yksinkertainen: tee nettisivu Chromeen ja tallenna se PDF-muodossa. Täydellinen laskujen, raporttien, sertifikaattien tai minkä tahansa dynaamisen sisällön tuottamiseen, joka pitää jakaa pdf-muodossa.
Tässä on peruslähestymistapa:
public class PdfGeneratorService
{
public async Task<byte[]> GeneratePdfFromUrlAsync(string url)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
});
await using var page = await browser.NewPageAsync();
await page.GoToAsync(url, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
});
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions
{
Top = "20mm",
Right = "20mm",
Bottom = "20mm",
Left = "20mm"
}
});
return pdfData;
}
}
Se näyttää yksinkertaiselta, kunnes ei ole.
Ongelma: Kauniit fonttisi eivät näy pdf:ssä, tai mikä pahempaa, ne ovat siellä, mutta näyttävät aivan surkeilta.
Miksi näin tapahtuu: Chrome tarvitsee pääsyn fonttitiedostoihin PDF-sukupolven aikana. Jos fonttisi ladataan ulkoisen CDN:n kautta, eikä Chrome pääse niihin (paloseinä, verkko-ongelmat, ajoitus), olet tukossa.
Ratkaisu:
await page.GoToAsync(url, new NavigationOptions
{
WaitUntil = new[]
{
WaitUntilNavigation.Networkidle0, // Wait for network to be idle
WaitUntilNavigation.Load // Wait for fonts to load
},
Timeout = 60000 // Give it time to load fonts
});
// Extra insurance - wait for fonts to actually load
await page.EvaluateFunctionAsync(@"async () => {
await document.fonts.ready;
}");
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true // CRUCIAL for @font-face fonts
});
Vielä parempi on, että isännöit fonttejasi paikallisesti tai liität ne CSS:ään base64:nä. Kyllä, se on kummallista, mutta luotettavaa.
Ongelma: PDF:si ei näytä lainkaan nettisivuiltasi, koska Chrome soveltaa painettuja mediakyselyitä.
Tämä on itse asiassa oikea käytös - PDF:t ovat printtimediaa, - mutta se nappaa kaikki ulos ensimmäisellä kerralla.
Ratkaisu:
Käyttö @media print CSS-säännöt ovat tarkoituksenmukaiset:
/* Show on screen, hide in PDF */
.no-print {
display: block;
}
@media print {
.no-print {
display: none !important;
}
/* Prevent page breaks inside elements */
.keep-together {
page-break-inside: avoid;
break-inside: avoid;
}
/* Force page breaks */
.page-break {
page-break-before: always;
}
}
Tai, jos haluat näyttöversion pdf-muodossasi (käyttää "kuvakaappauksia" PDF-muodossa):
await page.EmulateMediaTypeAsync(MediaType.Screen); // Force screen media
var pdfData = await page.PdfDataAsync();
Ongelma: Sisältösi jakautuu oudosti sivuille, ja otsikot ovat alareunassa tai pöydät on leikattu kahtia.
Todellisuus: Taistelet Chromen sisäistä paginaatioalgoritmia vastaan, ja se voittaa suurimman osan ajasta.
Mitä voit tehdä:
@media print {
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
break-after: avoid;
}
table, figure, img {
page-break-inside: avoid;
break-inside: avoid;
}
/* Force specific breaks */
.new-page {
page-break-before: always;
}
}
Ja PuppeteerSharp-koodissasi:
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
PreferCSSPageSize = true, // Respect CSS @page rules
DisplayHeaderFooter = false
});
Pro vinkki: Monimutkaisissa asetelmissa HTML:ää on joskus helpompi jäsentää selkeillä sivukatkoilla kuin taistella selainta vastaan:
<div class="page">
<!-- First page content -->
</div>
<div class="page-break"></div>
<div class="page">
<!-- Second page content -->
</div>
Otsikkoja ja foottereita voi lisätä, mutta API on hieman vinossa:
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
DisplayHeaderFooter = true,
HeaderTemplate = @"
<div style='font-size: 10px; text-align: center; width: 100%;'>
<span class='title'></span>
</div>
",
FooterTemplate = @"
<div style='font-size: 10px; text-align: center; width: 100%;'>
Page <span class='pageNumber'></span> of <span class='totalPages'></span>
</div>
",
MarginOptions = new MarginOptions
{
Top = "30mm", // Must be larger to accommodate header
Bottom = "25mm" // Must be larger to accommodate footer
}
});
Selvä.
date, title, url, pageNumber, totalPagesOletuksena, Chrome ei tulosta taustakuvia tai värejä (tämä on selaimen oletus musteen tallentamiselle). must mahdollistaa se:
var pdfData = await page.PdfDataAsync(new PdfOptions
{
PrintBackground = true // Without this, your beautiful backgrounds vanish
});
Ongelma: Monien pdf-tiedostojen tuottaminen saa sovelluksen muistin laukeamaan ja lopulta romahtamaan.
Miksi: Jokainen selain-installaatio käyttää merkittävää muistia (100-200MB), ja jos et hävitä kunnolla, ne kasaantuvat.
Ratkaisu:
Käytä aina await using tai kunnollinen hävittäminen:
// Good - automatic disposal
await using var browser = await Puppeteer.LaunchAsync(options);
await using var page = await browser.NewPageAsync();
// Or manually
IBrowser? browser = null;
try
{
browser = await Puppeteer.LaunchAsync(options);
// ... use browser
}
finally
{
if (browser != null)
{
await browser.CloseAsync();
await browser.DisposeAsync();
}
}
Suuritilavuusiselle PDF-sukupolvelle kannattaa harkita selaintapausten uudelleenkäyttöä:
public class PdfGeneratorService : IDisposable
{
private IBrowser? _browser;
private readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task<byte[]> GeneratePdfAsync(string url)
{
await _semaphore.WaitAsync();
try
{
// Reuse browser instance
_browser ??= await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await _browser.NewPageAsync();
await page.GoToAsync(url);
return await page.PdfDataAsync();
}
finally
{
_semaphore.Release();
}
}
public async ValueTask DisposeAsync()
{
if (_browser != null)
{
await _browser.CloseAsync();
await _browser.DisposeAsync();
}
_semaphore.Dispose();
}
public void Dispose()
{
DisposeAsync().AsTask().Wait();
}
}
Joskus sivulle pitää mahtua lisää sisältöä:
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
Scale = 0.8m, // 80% scale - fits more content
PrintBackground = true
});
Mutta ole varovainen - liian pieni ja se on lukukelvoton.
Näin itse asiassa teen pdf-sukupolven tuotannossa:
public class InvoicePdfGenerator
{
private readonly ILogger<InvoicePdfGenerator> _logger;
public InvoicePdfGenerator(ILogger<InvoicePdfGenerator> logger)
{
_logger = logger;
}
public async Task<byte[]> GenerateInvoicePdfAsync(Invoice invoice)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[]
{
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage" // Overcome limited resource problems
}
});
await using var page = await browser.NewPageAsync();
// Set up console logging to debug issues
page.Console += (_, e) =>
{
_logger.LogInformation("Browser console: {Message}", e.Message.Text);
};
try
{
// Generate HTML content (using Razor, or however you do it)
var htmlContent = await GenerateInvoiceHtmlAsync(invoice);
// Set content directly rather than navigating to URL
await page.SetContentAsync(htmlContent, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
});
// Wait for fonts to load
await page.EvaluateFunctionAsync("() => document.fonts.ready");
// Force screen media type to avoid print media queries changing layout
await page.EmulateMediaTypeAsync(MediaType.Screen);
// Generate PDF
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions
{
Top = "10mm",
Right = "10mm",
Bottom = "10mm",
Left = "10mm"
},
PreferCSSPageSize = false
});
_logger.LogInformation("Generated PDF for invoice {InvoiceId}, size: {Size} bytes",
invoice.Id, pdfData.Length);
return pdfData;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate PDF for invoice {InvoiceId}", invoice.Id);
// Take a screenshot for debugging
try
{
var screenshot = await page.ScreenshotDataAsync();
_logger.LogWarning("Captured screenshot of failed PDF generation: {Size} bytes",
screenshot.Length);
// Could save this to blob storage for debugging
}
catch
{
// Swallow screenshot errors
}
throw;
}
}
private async Task<string> GenerateInvoiceHtmlAsync(Invoice invoice)
{
// Your HTML generation logic here
// Could use Razor views, or any templating engine
return $@"
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
body {{
font-family: 'Inter', sans-serif;
margin: 0;
padding: 20px;
color: #333;
}}
@media print {{
.page-break {{
page-break-before: always;
}}
.no-break {{
page-break-inside: avoid;
}}
}}
</style>
</head>
<body>
<div class='no-break'>
<h1>Invoice #{invoice.Number}</h1>
<p>Date: {invoice.Date:yyyy-MM-dd}</p>
</div>
<!-- Invoice content -->
</body>
</html>";
}
}
Yksinkertaista, mutta usein tarpeellista:
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
Landscape = true, // Horizontal orientation
PrintBackground = true
});
Ei rajoituta vakiomuotoihin:
var pdfData = await page.PdfDataAsync(new PdfOptions
{
Width = "210mm", // Custom width
Height = "297mm", // Custom height (this is A4, but you can use any size)
PrintBackground = true
});
Testaamisen ja pdf-sukupolven lisäksi PuppeteerSharp on erinomainen useissa muissa automaatiotehtävissä. Tutustutaan yleisimpiin reaalimaailman sovelluksiin.
PuppeteerSharp on loistava raapimaan JavaScript-kovia sivustoja, joissa perinteiset HTML-parserit eivät riitä:
public class ProductScraper
{
public async Task<List<Product>> ScrapeProductsAsync(string url)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await browser.NewPageAsync();
await page.GoToAsync(url, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle2 }
});
// Wait for products to render (adjust selector as needed)
await page.WaitForSelectorAsync(".product-item");
// Extract product data using JavaScript
var products = await page.EvaluateFunctionAsync<List<Product>>(@"() => {
return Array.from(document.querySelectorAll('.product-item')).map(item => ({
name: item.querySelector('.product-name')?.textContent?.trim(),
price: parseFloat(item.querySelector('.product-price')?.textContent?.replace('£', '')),
imageUrl: item.querySelector('img')?.src,
inStock: !item.querySelector('.out-of-stock')
}));
}");
return products;
}
}
Milloin sitä käytetään:
EI saa käyttää:
Testaamisen lisäksi kuvakaappauksista on hyötyä esikatselussa, esikatselussa tai arkistoinnissa:
public class ScreenshotService
{
public async Task<byte[]> CaptureWebsiteAsync(string url, int width = 1920, int height = 1080)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await browser.NewPageAsync();
await page.SetViewportAsync(new ViewPortOptions
{
Width = width,
Height = height
});
await page.GoToAsync(url, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle2 }
});
// Full page screenshot
return await page.ScreenshotDataAsync(new ScreenshotOptions
{
FullPage = true,
Type = ScreenshotType.Png
});
}
public async Task<byte[]> CaptureElementAsync(string url, string selector)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await browser.NewPageAsync();
await page.GoToAsync(url);
var element = await page.WaitForSelectorAsync(selector);
if (element == null)
{
throw new InvalidOperationException($"Element {selector} not found");
}
// Screenshot of specific element
return await element.ScreenshotDataAsync();
}
}
Käytännöllisiä käyttötarkoituksia:
Mittaa sivun kantavuus:
public class PerformanceMonitor
{
public async Task<PerformanceMetrics> MeasurePagePerformanceAsync(string url)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await browser.NewPageAsync();
var stopwatch = Stopwatch.StartNew();
await page.GoToAsync(url, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle2 }
});
stopwatch.Stop();
// Get performance metrics from the browser
var metrics = await page.MetricsAsync();
// Get performance timing data
var performanceTiming = await page.EvaluateExpressionAsync<PerformanceTiming>(@"
JSON.parse(JSON.stringify(performance.timing))
");
return new PerformanceMetrics
{
TotalLoadTime = stopwatch.ElapsedMilliseconds,
DomContentLoaded = performanceTiming.DomContentLoadedEventEnd - performanceTiming.NavigationStart,
FirstPaint = metrics["FirstPaint"],
LayoutCount = (int)metrics["LayoutCount"],
ScriptDuration = metrics["ScriptDuration"]
};
}
}
Yhdistä HTML-tempaus PDF-sukupolveen automaattiseen raportointiin:
public class MonthlyReportGenerator
{
public async Task<byte[]> GenerateMonthlyReportAsync(ReportData data)
{
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await browser.NewPageAsync();
// Generate HTML report using your preferred templating engine
var html = GenerateReportHtml(data);
await page.SetContentAsync(html);
// Wait for any charts to render (if using Chart.js, D3.js, etc.)
await Task.Delay(2000);
return await page.PdfDataAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
DisplayHeaderFooter = true,
HeaderTemplate = $@"
<div style='font-size: 9px; margin: 0 auto; text-align: center;'>
Monthly Report - {data.Month:MMMM yyyy}
</div>
",
FooterTemplate = @"
<div style='font-size: 9px; margin: 0 auto; text-align: center;'>
Page <span class='pageNumber'></span> of <span class='totalPages'></span>
</div>
",
MarginOptions = new MarginOptions
{
Top = "25mm",
Bottom = "20mm",
Left = "15mm",
Right = "15mm"
}
});
}
}
PuppeteerSharpin käyttö PDF-sukupolvena on "ilmaista" siinä mielessä, että pdf-kirjaston käyttöluvasta ei makseta, mutta se on ei vapaa resurssien suhteen.
Jokainen selain-installaatio:
Vertaa tätä omistettuihin pdf-kirjastoihin, kuten:
Milloin PuppeteerSharpia käytetään PDF-muodossa:
Milloin käyttää omistettuja PDF-kirjastoja:
Joskus paras ratkaisu on käyttää molempia:
public class PdfService
{
private readonly ILogger<PdfService> _logger;
public async Task<byte[]> GeneratePdfAsync(PdfRequest request)
{
// Simple documents - use QuestPDF (fast, low resources)
if (request.IsSimpleLayout)
{
return GenerateWithQuestPdf(request);
}
// Complex documents with web content - use PuppeteerSharp
return await GenerateWithPuppeteerAsync(request);
}
private byte[] GenerateWithQuestPdf(PdfRequest request)
{
// QuestPDF code here - much faster for simple layouts
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Content().Text(request.Content);
});
}).GeneratePdf();
}
private async Task<byte[]> GenerateWithPuppeteerAsync(PdfRequest request)
{
// PuppeteerSharp code for complex layouts
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(request.HtmlContent);
return await page.PdfDataAsync();
}
}
PuppeteerSharp on ollut ehdoton pelinvaihtaja E2E-testauksessa .NET-projekteissani. Se on nopeampi kuin Selenium, siinä on modernimpi API, ja se tekee testaamisesta yleensä vähemmän työlästä.
Suosittelisin seuraavaa:
Aloita PuppeteerSharpista Jos testaat vain Chrome/Chromea, se on yksinkertaisempi ja nopeampi kuin vaihtoehdot.
Käytä Playwrightia Jos tarvitset usean selaimen tukea, se on saanut kaikki PuppeteerSharpin ja Firefoxin ja Safarin edut.
Vältä seleniumia Uusiin hankkeisiin, ellei sinulla ole erityistä syytä käyttää sitä (kuten IE11-tukea, jota toivottavasti ei).
Kirjoita testit harkiten. E2E-testit ovat hitaita ja voivat olla hauraita. Käytä niitä kriittisiin käyttäjämatkoihin, ei jokaisen pienen yksityiskohdan testaamiseen.
Pidä testit erilläänJokaisen kokeen tulisi laatia omat tietonsa ja siivota jälkensä.
Käytä auttajamenetelmiä Näyttämäni peruskurssin kaava pitää testikoodisi puhtaana ja keskittyy testaamaansa, ei testaamaansa.
E2E-testien ei tarvitse olla kivuliaita. Oikeiden työkalujen ja mallien avulla ne voivat olla itse asiassa varsin miellyttäviä. Anna PuppeteerSharpille mahdollisuus seuraavaan projektiin - olet varmasti iloisesti yllättynyt.
Menen kirjoittamaan lisää testejä.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.