forked from rnwood/smtp4dev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathazure-pipelines.yml
More file actions
1639 lines (1481 loc) · 79.4 KB
/
azure-pipelines.yml
File metadata and controls
1639 lines (1481 loc) · 79.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
trigger:
branches:
include:
- master
- refs/tags/*.*.*
- refs/tags/*.*.*.*
exclude:
- refs/tags/*-ci*
pr:
branches:
include:
- master
paths:
exclude:
- docs/*
- README.md
resources:
- repo: self
variables:
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}:
iscibuild: ${{ true }}
isreleasebuild: ${{ false }}
isprbuild: ${{ false }}
tag: $[ format('{0}{1:yyyyMMdd}{2}', variables['CIBUILDVERSIONPREFIX'], pipeline.startTime, counter(format('{0:yyyyMMdd}', pipeline.startTime), 100)) ]
version: $[ format('{0}{1:yyyyMMdd}{2}', variables['CIBUILDVERSIONPREFIX'], pipeline.startTime, counter(format('{0:yyyyMMdd}', pipeline.startTime), 100)) ]
${{ elseif startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}:
isreleasebuild: ${{ true }}
iscibuild: ${{ false }}
isprbuild: ${{ false }}
tag: $[ replace(variables['Build.SourceBranch'], 'refs/tags/', '') ]
version: $[ replace(variables['Build.SourceBranch'], 'refs/tags/', '') ]
${{ elseif startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:
isprbuild: ${{ true }}
iscibuild: ${{ false }}
isreleasebuild: ${{ false }}
tag: $[ format('{0}{1:yyyyMMdd}{2}_pr{3}', variables['CIBUILDVERSIONPREFIX'], pipeline.startTime, counter(format('{0:yyyyMMdd}', pipeline.startTime), 100), replace(replace(variables['Build.SourceBranch'], 'refs/pull/', ''), '/merge', '')) ]
version: $[ format('{0}{1:yyyyMMdd}{2}+pr{3}', variables['CIBUILDVERSIONPREFIX'], pipeline.startTime, counter(format('{0:yyyyMMdd}', pipeline.startTime), 100), replace(replace(variables['Build.SourceBranch'], 'refs/pull/', ''), '/merge', '')) ]
${{ else }}:
isprbuild: ${{ false }}
isreleasebuild: ${{ false }}
iscibuild: ${{ false }}
tag: $[ format('{0}{1:yyyyMMdd}{2}_{3}', variables['CIBUILDVERSIONPREFIX'], pipeline.startTime, counter(format('{0:yyyyMMdd}', pipeline.startTime), 100), replace(replace(variables['Build.SourceBranch'], 'refs/heads/', ''), '/', '-')) ]
version: $[ format('{0}{1:yyyyMMdd}{2}+{3}', variables['CIBUILDVERSIONPREFIX'], pipeline.startTime, counter(format('{0:yyyyMMdd}', pipeline.startTime), 100), replace(replace(variables['Build.SourceBranch'], 'refs/heads/', ''), '/', '-')) ]
docker_repo: "rnwood/smtp4dev"
docker_registry: "dockerhub-rnwood"
netcoresdk_version: "8.0.416"
# GitHub API token for PR notifications - should be configured as secret variable
# GITHUB_TOKEN: $(GITHUB_API_TOKEN)
# Build includes NuGet and npm package caching for improved performance
stages:
- stage: Init
displayName: Init
jobs:
- job: Init
pool:
vmImage: "ubuntu-22.04"
steps:
- checkout: none
- powershell: |
Write-Host "IsReleaseBuild=$(isreleasebuild) IsCIBuild=$(iscibuild) isprbuild=$(isprbuild)"
Write-Host "Tag=$(tag)"
Write-Host "Version=$(version)"
displayName: Display build details
- powershell: Write-Host "##vso[build.updatebuildnumber]$(tag) $(Build.BuildNumber)"
displayName: Update build details
- stage: Build
displayName: Build
condition: succeeded()
jobs:
- job: BuildMatrix
displayName: Build and test -
timeoutInMinutes: 90
pool:
vmImage: $(vmImage)
strategy:
matrix:
noruntime:
platformName: noruntime
buildArgs: ""
vmImage: "ubuntu-22.04"
runTests: ${{ false }}
win-x64:
platformName: win-x64
buildArgs: "-r win-x64 --self-contained -p:PublishSingleFile=true"
vmImage: "windows-2022"
runTests: ${{ true }}
linux-x64:
platformName: linux-x64
buildArgs: "-r linux-x64 --self-contained -p:PublishSingleFile=true"
vmImage: "ubuntu-22.04"
runTests: ${{ true }}
# osx-x64:
# platformName: osx-x64
# buildArgs: "-r osx-x64 --self-contained -p:PublishSingleFile=true"
# vmImage: "macOS-11"
# runTests: ${{ true }}
linux-musl-x64:
platformName: linux-musl-x64
buildArgs: "-r linux-musl-x64 --self-contained -p:PublishSingleFile=true"
vmImage: ubuntu-22.04
runTests: ${{ false }}
win-arm64:
platformName: win-arm64
buildArgs: "-r win-arm64 --self-contained -p:PublishSingleFile=true"
vmImage: windows-2022
runTests: ${{ false }}
linux-arm:
platformName: linux-arm
buildArgs: "-r linux-arm --self-contained -p:PublishSingleFile=true"
vmImage: ubuntu-22.04
runTests: ${{ false }}
steps:
- task: UseDotNet@2
displayName: Install .NET Core SDK v$(netcoresdk_version)
inputs:
packageType: sdk
version: $(netcoresdk_version)
# Cache NuGet packages based on all project files and solution
- task: Cache@2
displayName: Cache NuGet packages
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.nuget/packages
condition: ne(variables['Agent.OS'], 'Windows_NT')
# Windows-specific NuGet cache (different default path)
- task: Cache@2
displayName: Cache NuGet packages (Windows)
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(UserProfile)\.nuget\packages
condition: eq(variables['Agent.OS'], 'Windows_NT')
# Cache npm packages based on package-lock.json
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | Rnwood.Smtp4dev/ClientApp/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: Rnwood.Smtp4dev/ClientApp/node_modules
- task: DotNetCoreCLI@2
displayName: Build $(platformName)
inputs:
command: build
projects: Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj
publishWebProjects: false
arguments: '-c Release $(buildArgs) -p:version=$(version)'
env:
${{ if ne(variables['Agent.OS'], 'Windows_NT') }}:
NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages
- task: DotNetCoreCLI@2
displayName: Publish $(platformName)
inputs:
command: publish
projects: Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj
publishWebProjects: false
arguments: '-c Release $(buildArgs) -p:version=$(version) -o "$(Build.ArtifactStagingDirectory)/$(platformName)"'
env:
${{ if ne(variables['Agent.OS'], 'Windows_NT') }}:
NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages
- task: ExtractFiles@1
displayName: Extract files for E2E tests
condition: and(succeeded(), eq(variables['runTests'], true))
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/$(platformName)/Rnwood.Smtp4dev.zip'
destinationFolder: $(Agent.TempDirectory)/e2e
- task: DotNetCoreCLI@2
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Run SmtpServer Tests
inputs:
command: test
projects: smtpserver/Rnwood.SmtpServer.Tests
configuration: release
arguments: '--collect:"XPlat Code Coverage"'
publishTestResults: true
- task: DotNetCoreCLI@2
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Build Test Project for Playwright
inputs:
command: build
projects: Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj
arguments: '-c Release'
- task: PowerShell@2
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Install Playwright Browsers
timeoutInMinutes: 10
inputs:
targetType: inline
script: |
Write-Host "Installing Playwright browsers..."
try {
Write-Host "Current directory: $(Get-Location)"
Write-Host "Starting installation at: $(Get-Date)"
# Force a fresh build of the test project to ensure playwright.ps1 is generated
Write-Host "Ensuring test project is built with Playwright dependencies..."
dotnet build Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj -c Release --no-restore
# Wait briefly for file system to sync (CI timing issue)
Start-Sleep -Seconds 2
# Look for playwright.ps1 script in multiple possible locations
$possiblePaths = @(
"Rnwood.Smtp4dev.Tests/bin/Release/net8.0/playwright.ps1",
"Rnwood.Smtp4dev.Tests/bin/linux-x64/Release/net8.0/playwright.ps1",
"Rnwood.Smtp4dev.Tests/bin/win-x64/Release/net8.0/playwright.ps1"
)
$playwrightScript = $null
foreach ($path in $possiblePaths) {
Write-Host "Checking for Playwright script at: $path"
if (Test-Path $path) {
$playwrightScript = $path
Write-Host "✓ Found Playwright script at: $playwrightScript"
break
}
}
# If not found in expected locations, search recursively
if (-not $playwrightScript) {
Write-Host "Script not found in expected locations. Searching recursively..."
$foundScripts = Get-ChildItem -Path "." -Name "playwright.ps1" -Recurse -ErrorAction SilentlyContinue
if ($foundScripts) {
$playwrightScript = $foundScripts[0]
Write-Host "✓ Found Playwright script via recursive search at: $playwrightScript"
}
}
# Debug: Show build output structure if script still not found
if (-not $playwrightScript) {
Write-Host "❌ Playwright script not found. Investigating build output structure..."
if (Test-Path "Rnwood.Smtp4dev.Tests/bin") {
Write-Host "Contents of Rnwood.Smtp4dev.Tests/bin:"
Get-ChildItem "Rnwood.Smtp4dev.Tests/bin" -Recurse -File | Select-Object FullName | ForEach-Object { Write-Host " $($_.FullName)" }
} else {
Write-Host "Rnwood.Smtp4dev.Tests/bin directory not found"
Write-Host "Directory contents at root:"
Get-ChildItem "." | ForEach-Object { Write-Host " $_" }
}
}
if ($playwrightScript -and (Test-Path $playwrightScript)) {
Write-Host "Using test project's Playwright script for browser installation..."
# Install Chromium browser using project script
Write-Host "Installing Chromium browser..."
pwsh $playwrightScript install chromium
# Install system dependencies on Linux using project script
if ($IsLinux) {
Write-Host "Installing system dependencies on Linux..."
pwsh $playwrightScript install-deps chromium
}
} else {
Write-Host "Falling back to global CLI tool approach..."
# Fallback: Use global CLI tool approach as backup
Write-Host "Installing global Playwright CLI tool..."
dotnet tool install --global Microsoft.Playwright.CLI --version 1.2.3
Write-Host "Installing Chromium browser..."
playwright install chromium
if ($IsLinux) {
Write-Host "Installing system dependencies on Linux..."
playwright install-deps chromium
}
}
# Verify browser installation
Write-Host "Verifying browser installation..."
$playwrightCache = if ($IsWindows) { "$env:USERPROFILE\.cache\ms-playwright" } else { "$env:HOME/.cache/ms-playwright" }
Write-Host "Checking Playwright cache at: $playwrightCache"
if (Test-Path $playwrightCache) {
Write-Host "✓ Playwright cache directory exists"
$items = Get-ChildItem $playwrightCache -ErrorAction SilentlyContinue
if ($items) {
Write-Host "Cache contents:"
$items | ForEach-Object { Write-Host " $_" }
$chromiumDirs = $items | Where-Object { $_.Name -like "chromium-*" }
if ($chromiumDirs) {
Write-Host "✓ Chromium browser directories found: $($chromiumDirs.Name -join ', ')"
} else {
Write-Host "Warning: No chromium directories found in cache"
}
} else {
Write-Host "Warning: Playwright cache directory is empty"
}
} else {
Write-Host "Warning: Playwright cache directory not found at: $playwrightCache"
}
Write-Host "✓ Playwright installation completed successfully at: $(Get-Date)"
} catch {
Write-Error "Failed to install Playwright: $_"
Write-Host "Error details: $($_.Exception.Message)"
Write-Host "Error at: $(Get-Date)"
exit 1
}
- task: PowerShell@2
condition: and(always(), eq(variables['runTests'], true))
displayName: Create Playwright Reports Directory (HTML + Traces)
inputs:
targetType: inline
script: |
# Use Join-Path for cross-platform compatibility
$tempDir = "$(Agent.TempDirectory)"
$reportDir = Join-Path $tempDir "playwright-report"
$tracesDir = Join-Path $reportDir "traces"
Write-Host "Creating Playwright reports directory: $reportDir"
Write-Host "Creating Playwright traces directory: $tracesDir"
Write-Host "Platform: $([Environment]::OSVersion.Platform)"
Write-Host "Agent.TempDirectory: $tempDir"
if (-not (Test-Path $reportDir)) {
New-Item -ItemType Directory -Path $reportDir -Force
Write-Host "✓ Created directory: $reportDir"
} else {
Write-Host "✓ Directory already exists: $reportDir"
}
if (-not (Test-Path $tracesDir)) {
New-Item -ItemType Directory -Path $tracesDir -Force
Write-Host "✓ Created traces directory: $tracesDir"
} else {
Write-Host "✓ Traces directory already exists: $tracesDir"
}
# Verify the directories exist and are accessible
if (Test-Path $reportDir) {
$resolvedPath = Resolve-Path $reportDir
Write-Host "✓ Report directory verified at: $resolvedPath"
} else {
Write-Error "Failed to create or access directory: $reportDir"
exit 1
}
if (Test-Path $tracesDir) {
$resolvedTracesPath = Resolve-Path $tracesDir
Write-Host "✓ Traces directory verified at: $resolvedTracesPath"
} else {
Write-Error "Failed to create or access traces directory: $tracesDir"
exit 1
}
# Create an index.html file that includes links to both HTML reports and traces
$indexHtml = @"
<!DOCTYPE html>
<html>
<head>
<title>Playwright E2E Test Results - $(platformName)</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; border-bottom: 3px solid #007acc; padding-bottom: 10px; }
h2 { color: #555; margin-top: 30px; }
.section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.trace-info { background: #e7f3ff; padding: 15px; margin: 10px 0; border-radius: 5px; }
.instructions { background: #fff3cd; padding: 15px; margin: 10px 0; border-radius: 5px; }
ul { line-height: 1.6; }
.platform { color: #007acc; font-weight: bold; }
</style>
</head>
<body>
<div class='container'>
<h1>E2E Test Results - <span class='platform'>$(platformName)</span></h1>
<p><strong>Build:</strong> $(Build.BuildNumber) | <strong>Commit:</strong> $(Build.SourceVersion)</p>
<p>Generated on: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')</p>
<div class='section'>
<h2>🔍 Playwright Traces</h2>
<div class='trace-info'>
<p><strong>New Feature:</strong> This build includes comprehensive Playwright traces instead of simple screenshots.</p>
<p>Traces provide a complete debugging experience with:</p>
<ul>
<li><strong>Screenshots</strong> at every step</li>
<li><strong>DOM snapshots</strong> showing page state</li>
<li><strong>Network activity</strong> and API calls</li>
<li><strong>Console logs</strong> and JavaScript errors</li>
<li><strong>Source code</strong> mapping</li>
<li><strong>Timeline</strong> of all actions</li>
</ul>
</div>
<div class='instructions'>
<h3>How to View Traces:</h3>
<ol>
<li>Download the <code>PlaywrightReport_$(platformName)</code> artifact from Azure DevOps</li>
<li>Extract the ZIP file</li>
<li>Navigate to the <code>traces/</code> folder</li>
<li>Open any <code>.zip</code> trace file by:
<ul>
<li><strong>Online:</strong> Go to <a href='https://trace.playwright.dev' target='_blank'>trace.playwright.dev</a> and drag the trace ZIP file</li>
<li><strong>CLI:</strong> Run <code>npx playwright show-trace trace-file.zip</code></li>
</ul>
</li>
</ol>
</div>
</div>
<div class='section'>
<h2>📊 Test Execution Details</h2>
<p>E2E tests were executed via <code>dotnet test</code> with Playwright tracing enabled.</p>
<p>For test results and detailed logs, check the <strong>Tests</strong> tab in Azure DevOps.</p>
<p>Each test that runs generates a corresponding trace file for debugging.</p>
</div>
</div>
</body>
</html>
"@
$indexPath = Join-Path $reportDir "index.html"
$indexHtml | Out-File -FilePath $indexPath -Encoding UTF8
Write-Host "✓ Created comprehensive HTML report: $indexPath"
# Verify the file was created
if (Test-Path $indexPath) {
Write-Host "✓ HTML report file verified"
} else {
Write-Error "Failed to create HTML report file: $indexPath"
exit 1
}
- task: DotNetCoreCLI@2
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Run Tests
env:
SMTP4DEV_E2E_WORKINGDIR: $(Agent.TempDirectory)/e2e
SMTP4DEV_E2E_BINARY: $(Agent.TempDirectory)/e2e/Rnwood.Smtp4dev
# Note: This environment variable is not currently used by our E2E tests
# Our tests run via dotnet test, not playwright test runner directly
PLAYWRIGHT_HTML_REPORT: $(Agent.TempDirectory)/playwright-report
inputs:
command: test
projects: Rnwood.Smtp4dev.Tests
configuration: release
arguments: '--collect:"XPlat Code Coverage" --settings:Rnwood.Smtp4dev.Tests/playwright.runsettings'
publishTestResults: true
- task: PublishPipelineArtifact@1
condition: and(always(), eq(variables['runTests'], true))
displayName: Publish Playwright Reports and Traces
inputs:
targetPath: $(Agent.TempDirectory)/playwright-report
artifact: 'PlaywrightReport_$(platformName)'
publishLocation: 'pipeline'
- task: DotNetCoreCLI@2
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Install ReportGenerator Tool
inputs:
command: custom
custom: tool
arguments: 'install --global dotnet-reportgenerator-globaltool'
- task: PowerShell@2
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Generate Coverage Reports
inputs:
targetType: inline
script: |
Write-Host "Generating coverage reports..."
$coverageFiles = Get-ChildItem -Path "$(Agent.TempDirectory)" -Recurse -Include "coverage.cobertura.xml" | ForEach-Object { $_.FullName }
Write-Host "Found coverage files: $($coverageFiles -join ', ')"
if ($coverageFiles.Count -gt 0) {
$reportsArg = $coverageFiles -join ";"
$targetDir = "$(Agent.TempDirectory)/CoverageReports"
reportgenerator -reports:"$reportsArg" -targetdir:"$targetDir" -reporttypes:"Cobertura;HtmlInline_AzurePipelines;JsonSummary;Badges" -verbosity:Info
Write-Host "Coverage reports generated in: $targetDir"
# Display summary
if (Test-Path "$targetDir/Summary.json") {
$summary = Get-Content "$targetDir/Summary.json" | ConvertFrom-Json
Write-Host "Line Coverage: $($summary.summary.linecoverage)%"
Write-Host "Branch Coverage: $($summary.summary.branchcoverage)%"
Write-Host "Method Coverage: $($summary.summary.methodcoverage)%"
}
} else {
Write-Host "No coverage files found."
}
- task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['runTests'], true))
displayName: Publish Code Coverage Results
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/CoverageReports/Cobertura.xml'
reportDirectory: '$(Agent.TempDirectory)/CoverageReports'
failIfCoverageEmpty: false
- task: PublishPipelineArtifact@1
condition: and(succeeded(), eq(variables['runTests'], true), eq(variables['platformName'], 'linux-x64'))
displayName: Publish Coverage Reports as Artifact
inputs:
targetPath: '$(Agent.TempDirectory)/CoverageReports'
artifact: 'CoverageReports'
publishLocation: 'pipeline'
- powershell: move-item $(Build.ArtifactStagingDirectory)/$(platformName)/Rnwood.Smtp4dev.zip $(Build.ArtifactStagingDirectory)/$(platformName)/Rnwood.Smtp4dev-$(platformName)-$(tag).zip
displayName: Rename artifact
- publish: $(Build.ArtifactStagingDirectory)/$(platformName)
artifact: $(platformName)
- job: BuildGlobalTool
dependsOn:
displayName: Build - .NET Global Tool
pool:
vmImage: "windows-2022"
steps:
- task: UseDotNet@2
displayName: Install .NET Core SDK v$(netcoresdk_version)
inputs:
packageType: sdk
version: $(netcoresdk_version)
# Cache NuGet packages for Windows
- task: Cache@2
displayName: Cache NuGet packages
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(UserProfile)\.nuget\packages
# Cache npm packages
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | Rnwood.Smtp4dev/ClientApp/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: Rnwood.Smtp4dev/ClientApp/node_modules
- task: DotNetCoreCLI@2
displayName: Build .NET Core Global Tool
inputs:
command: custom
custom: pack
arguments: '-c Release -p:PackAsTool=true -p:version=$(version) -o "$(Build.ArtifactStagingDirectory)/dotnetglobaltool" Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj'
- publish: $(Build.ArtifactStagingDirectory)/dotnetglobaltool
artifact: dotnetglobaltool
- job: BuildDesktop
dependsOn:
displayName: Build - Desktop
pool:
vmImage: "windows-2022"
steps:
- task: UseDotNet@2
displayName: Install .NET Core SDK v$(netcoresdk_version)
inputs:
packageType: sdk
version: $(netcoresdk_version)
# Cache NuGet packages for Windows
- task: Cache@2
displayName: Cache NuGet packages
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(UserProfile)\.nuget\packages
# Cache npm packages
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | Rnwood.Smtp4dev/ClientApp/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: Rnwood.Smtp4dev/ClientApp/node_modules
- task: DotNetCoreCLI@2
displayName: Build .NET Core - win-x64-desktop
inputs:
command: publish
projects: "Rnwood.Smtp4dev.Desktop/Rnwood.Smtp4dev.Desktop.csproj"
publishWebProjects: false
arguments: '-c Release -r win-x64 -p:version=$(version) --self-contained -p:PublishSingleFile=true -o "$(Build.ArtifactStagingDirectory)/win-x64-desktop"'
- task: ExtractFiles@1
displayName: Extract files for E2E tests
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/win-x64-desktop/Rnwood.Smtp4dev.Desktop.zip'
destinationFolder: $(Agent.TempDirectory)/e2e
- task: DotNetCoreCLI@2
displayName: Run Tests
env:
SMTP4DEV_E2E_WORKINGDIR: $(Agent.TempDirectory)/e2e
SMTP4DEV_E2E_BINARY: $(Agent.TempDirectory)/e2e/Rnwood.Smtp4dev.Desktop
inputs:
command: test
projects: Rnwood.Smtp4dev.Desktop.Tests
configuration: release
arguments: '--collect:"XPlat Code Coverage"'
publishTestResults: true
- powershell: move-item $(Build.ArtifactStagingDirectory)/win-x64-desktop/Rnwood.Smtp4dev.Desktop.zip $(Build.ArtifactStagingDirectory)/win-x64-desktop/Rnwood.Smtp4dev.Desktop-win-x64-$(tag).zip
displayName: Rename artifact
- publish: $(Build.ArtifactStagingDirectory)/win-x64-desktop
artifact: win-x64-desktop
# - task: DotNetCoreCLI@2run
# displayName: Build .NET Core - linux-x64-desktop
# inputs:
# command: publish
# projects: 'Rnwood.Smtp4dev.Desktop/Rnwood.Smtp4dev.Desktop.csproj'
# publishWebProjects: false
# arguments: '-c Release -r linux-x64 -p:version=$(version) --self-contained -p:PublishSingleFile=true -o "$(Build.ArtifactStagingDirectory)/linux-x64-desktop"'
# - powershell: move-item $(Build.ArtifactStagingDirectory)/linux-x64-desktop/Rnwood.Smtp4dev.Desktop.zip $(Build.ArtifactStagingDirectory)/linux-x64-desktop/Rnwood.Smtp4dev.Desktop-linux-x64-$(tag).zip
# displayName: Rename artifact
# - publish: $(Build.ArtifactStagingDirectory)/linux-x64-desktop
# artifact: linux-x64-desktop
- job: DockerBuildLinux
displayName: DockerBuild - Linux
pool:
vmImage: "ubuntu-22.04"
steps:
- task: UseDotNet@2
displayName: Install .NET Core SDK v$(netcoresdk_version)
inputs:
packageType: sdk
version: $(netcoresdk_version)
# Cache NuGet packages
- task: Cache@2
displayName: Cache NuGet packages
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.nuget/packages
# Cache npm packages
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | Rnwood.Smtp4dev/ClientApp/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: Rnwood.Smtp4dev/ClientApp/node_modules
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
projects: Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
arguments: '-c Release -p:version=$(version) -o out'
- task: Docker@2
displayName: Build image
inputs:
command: build
dockerfile: "**/Dockerfile.linux"
arguments: "--build-arg version=$(version) -t $(docker_repo):linux-amd64-$(tag)"
- task: DotNetCoreCLI@2
displayName: Build Test Project for Playwright
inputs:
command: build
projects: Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj
arguments: '-c Release'
- task: PowerShell@2
displayName: Install Playwright Browsers
timeoutInMinutes: 10
inputs:
targetType: inline
script: |
Write-Host "Installing Playwright browsers..."
try {
Write-Host "Current directory: $(Get-Location)"
Write-Host "Starting installation at: $(Get-Date)"
# Wait briefly for file system to sync (CI timing issue)
Start-Sleep -Seconds 2
# Look for playwright.ps1 script in multiple possible locations
$possiblePaths = @(
"Rnwood.Smtp4dev.Tests/bin/Release/net8.0/playwright.ps1",
"Rnwood.Smtp4dev.Tests/bin/linux-x64/Release/net8.0/playwright.ps1",
"Rnwood.Smtp4dev.Tests/bin/win-x64/Release/net8.0/playwright.ps1"
)
$playwrightScript = $null
foreach ($path in $possiblePaths) {
Write-Host "Checking for Playwright script at: $path"
if (Test-Path $path) {
$playwrightScript = $path
Write-Host "✓ Found Playwright script at: $playwrightScript"
break
}
}
# If not found in expected locations, search recursively
if (-not $playwrightScript) {
Write-Host "Script not found in expected locations. Searching recursively..."
$foundScripts = Get-ChildItem -Path "." -Name "playwright.ps1" -Recurse -ErrorAction SilentlyContinue
if ($foundScripts) {
$playwrightScript = $foundScripts[0]
Write-Host "✓ Found Playwright script via recursive search at: $playwrightScript"
}
}
# Debug: Show build output structure if script still not found
if (-not $playwrightScript) {
Write-Host "❌ Playwright script not found. Investigating build output structure..."
if (Test-Path "Rnwood.Smtp4dev.Tests/bin") {
Write-Host "Contents of Rnwood.Smtp4dev.Tests/bin:"
Get-ChildItem "Rnwood.Smtp4dev.Tests/bin" -Recurse -File | Select-Object FullName | ForEach-Object { Write-Host " $($_.FullName)" }
} else {
Write-Host "Rnwood.Smtp4dev.Tests/bin directory not found"
Write-Host "Directory contents at root:"
Get-ChildItem "." | ForEach-Object { Write-Host " $_" }
}
}
if ($playwrightScript -and (Test-Path $playwrightScript)) {
Write-Host "Using test project's Playwright script for browser installation..."
# Install Chromium browser using project script
Write-Host "Installing Chromium browser..."
pwsh $playwrightScript install chromium
# Install system dependencies on Linux using project script
if ($IsLinux) {
Write-Host "Installing system dependencies on Linux..."
pwsh $playwrightScript install-deps chromium
}
} else {
Write-Host "Falling back to global CLI tool approach..."
# Fallback: Use global CLI tool approach as backup
Write-Host "Installing global Playwright CLI tool..."
dotnet tool install --global Microsoft.Playwright.CLI --version 1.2.3
Write-Host "Installing Chromium browser..."
playwright install chromium
if ($IsLinux) {
Write-Host "Installing system dependencies on Linux..."
playwright install-deps chromium
}
}
# Verify browser installation
Write-Host "Verifying browser installation..."
$playwrightCache = if ($IsWindows) { "$env:USERPROFILE\.cache\ms-playwright" } else { "$env:HOME/.cache/ms-playwright" }
Write-Host "Checking Playwright cache at: $playwrightCache"
if (Test-Path $playwrightCache) {
Write-Host "✓ Playwright cache directory exists"
$items = Get-ChildItem $playwrightCache -ErrorAction SilentlyContinue
if ($items) {
Write-Host "Cache contents:"
$items | ForEach-Object { Write-Host " $_" }
$chromiumDirs = $items | Where-Object { $_.Name -like "chromium-*" }
if ($chromiumDirs) {
Write-Host "✓ Chromium browser directories found: $($chromiumDirs.Name -join ', ')"
} else {
Write-Host "Warning: No chromium directories found in cache"
}
} else {
Write-Host "Warning: Playwright cache directory is empty"
}
} else {
Write-Host "Warning: Playwright cache directory not found at: $playwrightCache"
}
Write-Host "✓ Playwright installation completed successfully at: $(Get-Date)"
} catch {
Write-Error "Failed to install Playwright: $_"
Write-Host "Error details: $($_.Exception.Message)"
Write-Host "Error at: $(Get-Date)"
exit 1
}
- task: DotNetCoreCLI@2
displayName: Run E2E Tests on Docker Image
env:
SMTP4DEV_E2E_WORKINGDIR: $(Agent.TempDirectory)
SMTP4DEV_E2E_USEDEFAULTDBPATH: 1
SMTP4DEV_E2E_BINARY: docker
SMTP4DEV_E2E_ARGS: |
run
-i
-p
2525:25
-p
5000:80
-p
1143:143
-p
1100:110
$(docker_repo):linux-amd64-$(tag)
--urls=http://*:80
--smtpport=25
--imapport=143
--pop3port=110
inputs:
command: test
projects: Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj
arguments: '--configuration Release --logger trx'
- task: Docker@2
displayName: Push image
condition: or(eq(variables['isreleasebuild'], true), eq(variables['iscibuild'], true), eq(variables['isprbuild'], true))
inputs:
containerRegistry: $(docker_registry)
repository: $(docker_repo)
command: push
tags: |
linux-amd64-$(tag)
- job: DockerBuildLinuxArm64
displayName: DockerBuild - Linux - ARM64
pool:
vmImage: "ubuntu-22.04"
steps:
- task: UseDotNet@2
displayName: Install .NET Core SDK v$(netcoresdk_version)
inputs:
packageType: sdk
version: $(netcoresdk_version)
# Cache NuGet packages
- task: Cache@2
displayName: Cache NuGet packages
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.nuget/packages
# Cache npm packages
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | Rnwood.Smtp4dev/ClientApp/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: Rnwood.Smtp4dev/ClientApp/node_modules
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
projects: Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
arguments: '-c Release -p:version=$(version) -o out'
- task: CmdLine@2
displayName: 'Install emulator'
inputs:
script: 'sudo apt-get update && sudo apt-get install -y qemu qemu-user-static'
- task: Docker@2
displayName: Build image
inputs:
command: build
dockerfile: "**/Dockerfile.linux.arm64"
arguments: "--platform linux/arm64 --build-arg version=$(version) -t $(docker_repo):linux-arm64-$(tag)"
- task: DotNetCoreCLI@2
displayName: Build Test Project
inputs:
command: build
projects: Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj
arguments: '-c Release'
- task: DotNetCoreCLI@2
displayName: Run E2E Tests on Docker Image
env:
SMTP4DEV_E2E_WORKINGDIR: $(Agent.TempDirectory)
SMTP4DEV_E2E_USEDEFAULTDBPATH: 1
SMTP4DEV_E2E_BINARY: docker
SMTP4DEV_E2E_ARGS: |
run
-i
-p
2525:25
-p
5000:80
-p
1143:143
-p
1100:110
$(docker_repo):linux-arm64-$(tag)
--urls=http://*:80
--smtpport=25
--imapport=143
--pop3port=110
inputs:
command: test
projects: Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj
arguments: '--configuration Release --logger trx --filter "FullyQualifiedName!~Rnwood.Smtp4dev.Tests.E2E.WebUI"'
- task: Docker@2
displayName: Push image
condition: or(eq(variables['isreleasebuild'], true), eq(variables['iscibuild'], true), eq(variables['isprbuild'], true))
inputs:
containerRegistry: $(docker_registry)
repository: $(docker_repo)
command: push
tags: |
linux-arm64-$(tag)
- job: DockerBuildWindows
displayName: DockerBuild - Windows LTSC 2019
pool:
vmImage: "windows-2022"
steps:
- task: UseDotNet@2
displayName: Install .NET Core SDK v$(netcoresdk_version)
inputs:
packageType: sdk
version: $(netcoresdk_version)
# Cache NuGet packages for Windows
- task: Cache@2
displayName: Cache NuGet packages
inputs:
key: 'nuget | "$(Agent.OS)" | "$(netcoresdk_version)" | **/*.csproj'
restoreKeys: |
nuget | "$(Agent.OS)" | "$(netcoresdk_version)"
nuget | "$(Agent.OS)"
path: $(UserProfile)\.nuget\packages
# Cache npm packages
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | Rnwood.Smtp4dev/ClientApp/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: Rnwood.Smtp4dev/ClientApp/node_modules
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
projects: Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
arguments: '-c Release -p:version=$(version) -r win-x64 --self-contained -o out'
- task: Docker@2
displayName: Build image
inputs:
command: build
dockerfile: "**/Dockerfile.windows.ltsc2019"
arguments: "--build-arg version=$(version) -t $(docker_repo):windows-ltsc2019-amd64-$(tag)"
- task: Docker@2
displayName: Build image
inputs:
command: build
dockerfile: "**/Dockerfile.windows.ltsc2022"
arguments: "--build-arg version=$(version) -t $(docker_repo):windows-ltsc2022-amd64-$(tag)"
- task: DotNetCoreCLI@2
displayName: Build Test Project for Playwright
inputs:
command: build
projects: Rnwood.Smtp4dev.Tests/Rnwood.Smtp4dev.Tests.csproj
arguments: '-c Release'
- task: PowerShell@2
displayName: Install Playwright Browsers
timeoutInMinutes: 10
inputs:
targetType: inline
script: |
Write-Host "Installing Playwright browsers..."
try {
Write-Host "Current directory: $(Get-Location)"
Write-Host "Starting installation at: $(Get-Date)"
# Wait briefly for file system to sync (CI timing issue)
Start-Sleep -Seconds 2
# Look for playwright.ps1 script in multiple possible locations
$possiblePaths = @(
"Rnwood.Smtp4dev.Tests/bin/Release/net8.0/playwright.ps1",
"Rnwood.Smtp4dev.Tests/bin/linux-x64/Release/net8.0/playwright.ps1",
"Rnwood.Smtp4dev.Tests/bin/win-x64/Release/net8.0/playwright.ps1"
)
$playwrightScript = $null
foreach ($path in $possiblePaths) {
Write-Host "Checking for Playwright script at: $path"
if (Test-Path $path) {
$playwrightScript = $path