agents-cli-automation 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -0
- package/package.json +2 -2
- package/src/commands/init.js +74 -9
- package/src/templates/playwright-agent-api.md +190 -0
- package/src/templates/playwright-agent-csharp.md +521 -0
- package/src/templates/playwright-agent-java.md +471 -0
- package/src/templates/playwright-agent-js.md +461 -0
- package/src/templates/playwright-agent-ts.md +1419 -0
- package/src/templates/playwright-agent.md +1359 -52
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
name: Create Playwright Framework - UI Testing (C#)
|
|
2
|
+
description: Creates a production-ready Playwright UI automation framework in C# with .NET, NUnit, and all required configurations.
|
|
3
|
+
argument-hint: "framework requirements, e.g., 'C# with Playwright tests'"
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Playwright UI Testing Framework - C#
|
|
8
|
+
|
|
9
|
+
This agent creates a production-ready Playwright framework in C# with .NET and NUnit optimized for UI automation.
|
|
10
|
+
|
|
11
|
+
## Capabilities
|
|
12
|
+
- **.NET 8+** - Chromium only with modern C# patterns
|
|
13
|
+
- **NUnit** - Latest testing framework
|
|
14
|
+
- **C# 12** - Latest language features and async/await
|
|
15
|
+
- **Page Object Model (POM)** - Reusable page classes
|
|
16
|
+
- **Dependency Injection** - Built with NUnit setup
|
|
17
|
+
- **Parallel execution** - Tests run simultaneously
|
|
18
|
+
- **Test data support** - JSON, CSV, YAML loading
|
|
19
|
+
- **BDD Support** - SpecFlow with Gherkin syntax
|
|
20
|
+
- **Explicit waits** - Proper synchronization
|
|
21
|
+
- **Scalable folder structure** with best practices
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
- .NET 8 SDK installed
|
|
25
|
+
- Visual Studio 2022 or VS Code with C# extension
|
|
26
|
+
- Git (optional)
|
|
27
|
+
|
|
28
|
+
## Setup Instructions
|
|
29
|
+
|
|
30
|
+
### 1. Create .NET Project
|
|
31
|
+
```bash
|
|
32
|
+
dotnet new classlib -n PlaywrightFramework
|
|
33
|
+
cd PlaywrightFramework
|
|
34
|
+
dotnet new nunit --force
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Update .csproj with Dependencies
|
|
38
|
+
```xml
|
|
39
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
40
|
+
<PropertyGroup>
|
|
41
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
42
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
43
|
+
<Nullable>enable</Nullable>
|
|
44
|
+
<LangVersion>latest</LangVersion>
|
|
45
|
+
</PropertyGroup>
|
|
46
|
+
|
|
47
|
+
<ItemGroup>
|
|
48
|
+
<PackageReference Include="Microsoft.Playwright" Version="1.50.0" />
|
|
49
|
+
<PackageReference Include="NUnit" Version="4.1.0" />
|
|
50
|
+
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
|
51
|
+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.2" />
|
|
52
|
+
|
|
53
|
+
<!-- JSON -->
|
|
54
|
+
<PackageReference Include="System.Text.Json" Version="8.0.0" />
|
|
55
|
+
|
|
56
|
+
<!-- YAML -->
|
|
57
|
+
<PackageReference Include="YamlDotNet" Version="13.0.1" />
|
|
58
|
+
|
|
59
|
+
<!-- CSV -->
|
|
60
|
+
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
|
61
|
+
|
|
62
|
+
<!-- SpecFlow (BDD) -->
|
|
63
|
+
<PackageReference Include="SpecFlow" Version="3.9.74" />
|
|
64
|
+
<PackageReference Include="SpecFlow.NUnit" Version="3.9.74" />
|
|
65
|
+
</ItemGroup>
|
|
66
|
+
</Project>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Project Structure
|
|
70
|
+
```
|
|
71
|
+
PlaywrightFramework/
|
|
72
|
+
├── Pages/
|
|
73
|
+
│ ├── BasePage.cs
|
|
74
|
+
│ ├── LoginPage.cs
|
|
75
|
+
│ ├── InventoryPage.cs
|
|
76
|
+
│ └── FormPage.cs
|
|
77
|
+
├── Utils/
|
|
78
|
+
│ ├── DataReader.cs
|
|
79
|
+
│ ├── FormHelper.cs
|
|
80
|
+
│ └── TestDataManager.cs
|
|
81
|
+
├── Models/
|
|
82
|
+
│ ├── User.cs
|
|
83
|
+
│ ├── FormData.cs
|
|
84
|
+
│ └── Config.cs
|
|
85
|
+
├── Fixtures/
|
|
86
|
+
│ └── TestFixtures.cs
|
|
87
|
+
├── Tests/
|
|
88
|
+
│ ├── SauceDemoTests.cs
|
|
89
|
+
│ └── FormTests.cs
|
|
90
|
+
├── StepDefinitions/
|
|
91
|
+
│ ├── LoginSteps.cs
|
|
92
|
+
│ └── ShoppingSteps.cs
|
|
93
|
+
├── Features/
|
|
94
|
+
│ ├── login.feature
|
|
95
|
+
│ └── shopping.feature
|
|
96
|
+
├── Data/
|
|
97
|
+
│ ├── users.json
|
|
98
|
+
│ ├── formData.csv
|
|
99
|
+
│ └── config.yaml
|
|
100
|
+
├── specflow.json
|
|
101
|
+
└── PlaywrightFramework.csproj
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4. Base Page (Pages/BasePage.cs)
|
|
105
|
+
```csharp
|
|
106
|
+
using Microsoft.Playwright;
|
|
107
|
+
|
|
108
|
+
namespace PlaywrightFramework.Pages
|
|
109
|
+
{
|
|
110
|
+
public class BasePage
|
|
111
|
+
{
|
|
112
|
+
protected IPage Page;
|
|
113
|
+
private const int Timeout = 5000;
|
|
114
|
+
|
|
115
|
+
public BasePage(IPage page)
|
|
116
|
+
{
|
|
117
|
+
Page = page;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public async Task NavigateToAsync(string url)
|
|
121
|
+
{
|
|
122
|
+
await Page.GotoAsync(url);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async Task ClickAsync(string selector)
|
|
126
|
+
{
|
|
127
|
+
await Page.ClickAsync(selector);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public async Task FillAsync(string selector, string text)
|
|
131
|
+
{
|
|
132
|
+
await Page.FillAsync(selector, text);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async Task<string?> GetTextAsync(string selector)
|
|
136
|
+
{
|
|
137
|
+
return await Page.TextContentAsync(selector);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public async Task WaitForElementAsync(string selector)
|
|
141
|
+
{
|
|
142
|
+
await Page.WaitForSelectorAsync(selector, new() { Timeout = Timeout });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public async Task<bool> IsVisibleAsync(string selector)
|
|
146
|
+
{
|
|
147
|
+
return await Page.Locator(selector).IsVisibleAsync();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public async Task SelectDropdownAsync(string selector, string optionText)
|
|
151
|
+
{
|
|
152
|
+
await Page.ClickAsync(selector);
|
|
153
|
+
await Page.ClickAsync($"text={optionText}");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public async Task SelectRadioButtonAsync(string labelText)
|
|
157
|
+
{
|
|
158
|
+
var label = Page.Locator($"label:has-text(\"{labelText}\")");
|
|
159
|
+
var radioId = await label.GetAttributeAsync("for");
|
|
160
|
+
await Page.ClickAsync($"#{radioId}");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public async Task CheckCheckboxAsync(string labelText)
|
|
164
|
+
{
|
|
165
|
+
var label = Page.Locator($"label:has-text(\"{labelText}\")");
|
|
166
|
+
var checkboxId = await label.GetAttributeAsync("for");
|
|
167
|
+
var checkbox = Page.Locator($"#{checkboxId}");
|
|
168
|
+
var isChecked = await checkbox.IsCheckedAsync();
|
|
169
|
+
|
|
170
|
+
if (!isChecked)
|
|
171
|
+
{
|
|
172
|
+
await checkbox.ClickAsync();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 5. Login Page (Pages/LoginPage.cs)
|
|
180
|
+
```csharp
|
|
181
|
+
using Microsoft.Playwright;
|
|
182
|
+
|
|
183
|
+
namespace PlaywrightFramework.Pages
|
|
184
|
+
{
|
|
185
|
+
public class LoginPage : BasePage
|
|
186
|
+
{
|
|
187
|
+
private const string UsernameField = "[data-test=\"username\"]";
|
|
188
|
+
private const string PasswordField = "[data-test=\"password\"]";
|
|
189
|
+
private const string LoginButton = "[data-test=\"login-button\"]";
|
|
190
|
+
private const string AppUrl = "https://www.saucedemo.com/";
|
|
191
|
+
|
|
192
|
+
public LoginPage(IPage page) : base(page)
|
|
193
|
+
{
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public async Task GoToAsync()
|
|
197
|
+
{
|
|
198
|
+
await Page.GotoAsync(AppUrl);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public async Task LoginAsync(string username, string password)
|
|
202
|
+
{
|
|
203
|
+
await Page.FillAsync(UsernameField, username);
|
|
204
|
+
await Page.FillAsync(PasswordField, password);
|
|
205
|
+
await Page.ClickAsync(LoginButton);
|
|
206
|
+
await Page.WaitForURLAsync("**/inventory.html");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public async Task<bool> IsLoadedAsync()
|
|
210
|
+
{
|
|
211
|
+
return await Page.Locator(UsernameField).IsVisibleAsync();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 6. Data Reader (Utils/DataReader.cs)
|
|
218
|
+
```csharp
|
|
219
|
+
using System.Text.Json;
|
|
220
|
+
using YamlDotNet.Serialization;
|
|
221
|
+
using CsvHelper;
|
|
222
|
+
using System.Globalization;
|
|
223
|
+
|
|
224
|
+
namespace PlaywrightFramework.Utils
|
|
225
|
+
{
|
|
226
|
+
public class DataReader
|
|
227
|
+
{
|
|
228
|
+
private static readonly string DataDir = Path.Combine(
|
|
229
|
+
AppContext.BaseDirectory,
|
|
230
|
+
"..", "..", "..", "Data"
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
public static async Task<Dictionary<string, object>> ReadJsonAsync(string filename)
|
|
234
|
+
{
|
|
235
|
+
var path = Path.Combine(DataDir, filename);
|
|
236
|
+
var content = await File.ReadAllTextAsync(path);
|
|
237
|
+
return JsonSerializer.Deserialize<Dictionary<string, object>>(content)
|
|
238
|
+
?? new Dictionary<string, object>();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public static async Task<Dictionary<string, object>> ReadYamlAsync(string filename)
|
|
242
|
+
{
|
|
243
|
+
var path = Path.Combine(DataDir, filename);
|
|
244
|
+
var content = await File.ReadAllTextAsync(path);
|
|
245
|
+
var deserializer = new Deserializer();
|
|
246
|
+
return deserializer.Deserialize<Dictionary<string, object>>(content)
|
|
247
|
+
?? new Dictionary<string, object>();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public static async Task<List<Dictionary<string, string>>> ReadCsvAsync(string filename)
|
|
251
|
+
{
|
|
252
|
+
var path = Path.Combine(DataDir, filename);
|
|
253
|
+
var records = new List<Dictionary<string, string>>();
|
|
254
|
+
|
|
255
|
+
using (var reader = new StreamReader(path))
|
|
256
|
+
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
|
|
257
|
+
{
|
|
258
|
+
csv.Read();
|
|
259
|
+
csv.ReadHeader();
|
|
260
|
+
|
|
261
|
+
while (csv.Read())
|
|
262
|
+
{
|
|
263
|
+
var record = new Dictionary<string, string>();
|
|
264
|
+
foreach (var header in csv.HeaderRecord!)
|
|
265
|
+
{
|
|
266
|
+
record[header] = csv.GetField(header) ?? string.Empty;
|
|
267
|
+
}
|
|
268
|
+
records.Add(record);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return records;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
public static Dictionary<string, string>? GetRecordByKey(
|
|
276
|
+
List<Dictionary<string, string>> data,
|
|
277
|
+
string key,
|
|
278
|
+
string value)
|
|
279
|
+
{
|
|
280
|
+
return data.FirstOrDefault(record =>
|
|
281
|
+
record.ContainsKey(key) && record[key] == value);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 7. Form Helper (Utils/FormHelper.cs)
|
|
288
|
+
```csharp
|
|
289
|
+
using Microsoft.Playwright;
|
|
290
|
+
|
|
291
|
+
namespace PlaywrightFramework.Utils
|
|
292
|
+
{
|
|
293
|
+
public class FormHelper
|
|
294
|
+
{
|
|
295
|
+
private readonly IPage _page;
|
|
296
|
+
|
|
297
|
+
public FormHelper(IPage page)
|
|
298
|
+
{
|
|
299
|
+
_page = page;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public async Task FillInputAsync(string selector, string value)
|
|
303
|
+
{
|
|
304
|
+
await _page.FillAsync(selector, value);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public async Task FillTextAreaAsync(string selector, string value)
|
|
308
|
+
{
|
|
309
|
+
var locator = _page.Locator(selector);
|
|
310
|
+
await locator.ClearAsync();
|
|
311
|
+
await locator.TypeAsync(value, new() { Delay = 50 });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public async Task SelectDropdownAsync(string dropdownSelector, string optionText)
|
|
315
|
+
{
|
|
316
|
+
await _page.ClickAsync(dropdownSelector);
|
|
317
|
+
await _page.ClickAsync($"text={optionText}");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public async Task SelectRadioButtonAsync(string labelText)
|
|
321
|
+
{
|
|
322
|
+
var label = _page.Locator($"label:has-text(\"{labelText}\")");
|
|
323
|
+
var radioId = await label.GetAttributeAsync("for");
|
|
324
|
+
await _page.ClickAsync($"#{radioId}");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
public async Task CheckCheckboxAsync(string labelText)
|
|
328
|
+
{
|
|
329
|
+
var label = _page.Locator($"label:has-text(\"{labelText}\")");
|
|
330
|
+
var checkboxId = await label.GetAttributeAsync("for");
|
|
331
|
+
var checkbox = _page.Locator($"#{checkboxId}");
|
|
332
|
+
|
|
333
|
+
if (!await checkbox.IsCheckedAsync())
|
|
334
|
+
{
|
|
335
|
+
await checkbox.ClickAsync();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public async Task CheckMultipleAsync(List<string> labels)
|
|
340
|
+
{
|
|
341
|
+
foreach (var label in labels)
|
|
342
|
+
{
|
|
343
|
+
await CheckCheckboxAsync(label);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
public async Task<string> GetInputValueAsync(string selector)
|
|
348
|
+
{
|
|
349
|
+
return await _page.InputValueAsync(selector);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
public async Task<bool> IsCheckboxCheckedAsync(string labelText)
|
|
353
|
+
{
|
|
354
|
+
var label = _page.Locator($"label:has-text(\"{labelText}\")");
|
|
355
|
+
var checkboxId = await label.GetAttributeAsync("for");
|
|
356
|
+
return await _page.Locator($"#{checkboxId}").IsCheckedAsync();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### 8. Test Base Fixture (Fixtures/TestFixtures.cs)
|
|
363
|
+
```csharp
|
|
364
|
+
using Microsoft.Playwright;
|
|
365
|
+
|
|
366
|
+
namespace PlaywrightFramework.Fixtures
|
|
367
|
+
{
|
|
368
|
+
public class TestFixtures
|
|
369
|
+
{
|
|
370
|
+
protected IPlaywright Playwright = null!;
|
|
371
|
+
protected IBrowser Browser = null!;
|
|
372
|
+
protected IBrowserContext Context = null!;
|
|
373
|
+
protected IPage Page = null!;
|
|
374
|
+
|
|
375
|
+
[SetUp]
|
|
376
|
+
public async Task SetupAsync()
|
|
377
|
+
{
|
|
378
|
+
Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
|
|
379
|
+
Browser = await Playwright.Chromium.LaunchAsync();
|
|
380
|
+
Context = await Browser.NewContextAsync();
|
|
381
|
+
Page = await Context.NewPageAsync();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
[TearDown]
|
|
385
|
+
public async Task TeardownAsync()
|
|
386
|
+
{
|
|
387
|
+
if (Page != null)
|
|
388
|
+
await Page.CloseAsync();
|
|
389
|
+
|
|
390
|
+
if (Context != null)
|
|
391
|
+
await Context.CloseAsync();
|
|
392
|
+
|
|
393
|
+
if (Browser != null)
|
|
394
|
+
await Browser.CloseAsync();
|
|
395
|
+
|
|
396
|
+
if (Playwright != null)
|
|
397
|
+
Playwright.Dispose();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### 9. Sample Test (Tests/SauceDemoTests.cs)
|
|
404
|
+
```csharp
|
|
405
|
+
using NUnit.Framework;
|
|
406
|
+
using PlaywrightFramework.Pages;
|
|
407
|
+
using PlaywrightFramework.Fixtures;
|
|
408
|
+
|
|
409
|
+
namespace PlaywrightFramework.Tests
|
|
410
|
+
{
|
|
411
|
+
[TestFixture]
|
|
412
|
+
public class SauceDemoTests : TestFixtures
|
|
413
|
+
{
|
|
414
|
+
[Test]
|
|
415
|
+
public async Task TestLoginWithValidCredentials()
|
|
416
|
+
{
|
|
417
|
+
var loginPage = new LoginPage(Page);
|
|
418
|
+
await loginPage.GoToAsync();
|
|
419
|
+
await loginPage.LoginAsync("standard_user", "secret_sauce");
|
|
420
|
+
|
|
421
|
+
Assert.That(Page.Url, Does.Contain("inventory.html"));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
[Test]
|
|
425
|
+
public async Task TestLoginWithInvalidCredentials()
|
|
426
|
+
{
|
|
427
|
+
var loginPage = new LoginPage(Page);
|
|
428
|
+
await loginPage.GoToAsync();
|
|
429
|
+
|
|
430
|
+
await loginPage.LoginAsync("invalid_user", "wrong_password");
|
|
431
|
+
|
|
432
|
+
var errorMessage = await loginPage.GetTextAsync("[data-test=\"error\"]");
|
|
433
|
+
Assert.That(errorMessage, Does.Contain("Username and password do not match"));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 10. Run Tests
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
# Run all tests
|
|
443
|
+
dotnet test
|
|
444
|
+
|
|
445
|
+
# Run specific test class
|
|
446
|
+
dotnet test --filter=SauceDemoTests
|
|
447
|
+
|
|
448
|
+
# Run specific test method
|
|
449
|
+
dotnet test --filter="Name~TestLoginWithValidCredentials"
|
|
450
|
+
|
|
451
|
+
# Run with parallel execution (default)
|
|
452
|
+
dotnet test --parallel
|
|
453
|
+
|
|
454
|
+
# Run with NUnit console runner
|
|
455
|
+
nunit3-console PlaywrightFramework.csproj
|
|
456
|
+
|
|
457
|
+
# Run BDD scenarios
|
|
458
|
+
dotnet test --filter=BDD
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### 11. Test Data Files
|
|
462
|
+
|
|
463
|
+
**Data/users.json**
|
|
464
|
+
```json
|
|
465
|
+
{
|
|
466
|
+
"users": [
|
|
467
|
+
{
|
|
468
|
+
"id": "1",
|
|
469
|
+
"username": "standard_user",
|
|
470
|
+
"password": "secret_sauce",
|
|
471
|
+
"email": "user@example.com",
|
|
472
|
+
"firstName": "John",
|
|
473
|
+
"lastName": "Doe"
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**Data/config.yaml**
|
|
480
|
+
```yaml
|
|
481
|
+
app:
|
|
482
|
+
url: https://www.saucedemo.com/
|
|
483
|
+
timeout: 30000
|
|
484
|
+
users:
|
|
485
|
+
standard:
|
|
486
|
+
username: standard_user
|
|
487
|
+
password: secret_sauce
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Data/formData.csv**
|
|
491
|
+
```csv
|
|
492
|
+
formType,firstName,lastName,email
|
|
493
|
+
basic,John,Doe,john@example.com
|
|
494
|
+
extended,Jane,Smith,jane@example.com
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 12. specflow.json (BDD Configuration)
|
|
498
|
+
```json
|
|
499
|
+
{
|
|
500
|
+
"language": {
|
|
501
|
+
"feature": "en"
|
|
502
|
+
},
|
|
503
|
+
"stepAssembly": "PlaywrightFramework",
|
|
504
|
+
"unitTestProvider": "nunit",
|
|
505
|
+
"bindingCulture": "en-US"
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Best Practices
|
|
510
|
+
✅ **Page Object Model** - All selectors in page classes
|
|
511
|
+
✅ **Async/Await** - Modern C# patterns throughout
|
|
512
|
+
✅ **Dependency Injection** - NUnit fixtures
|
|
513
|
+
✅ **Test Data** - Externalize in JSON, CSV, YAML
|
|
514
|
+
✅ **Form Helpers** - Handle all form interactions
|
|
515
|
+
✅ **Parallel Execution** - Default test runner behavior
|
|
516
|
+
✅ **Chromium Only** - Single browser for consistency
|
|
517
|
+
✅ **C# 12** - Latest language features
|
|
518
|
+
✅ **NUnit** - Modern testing framework
|
|
519
|
+
✅ **.NET 8** - Latest framework version
|
|
520
|
+
|
|
521
|
+
**Note:** This framework uses .NET 8, C# 12, Chromium only, and async/await patterns. Use `dotnet build` to compile and `dotnet test` to run tests with automatic parallel execution.
|