diff --git a/.artifactignore b/.artifactignore
index a287e295ae..ac78eeca46 100644
--- a/.artifactignore
+++ b/.artifactignore
@@ -1,3 +1,3 @@
**/*
-!**/bin/**
-!**/obj/**
+!tests/Umbraco.Tests.Integration/bin/**
+!tests/Umbraco.Tests.UnitTests/bin/**
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index 0cacf10718..3d2b10e614 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -5,6 +5,10 @@ parameters:
displayName: Run SQL Server Integration Tests
type: boolean
default: false
+ - name: sqlServerAcceptanceTests
+ displayName: Run SQL Server Acceptance Tests
+ type: boolean
+ default: false
- name: myGetDeploy
displayName: Deploy to MyGet
type: boolean
@@ -34,11 +38,11 @@ parameters:
type: string
default: ' '
- name: nonWindowsIntegrationNonReleaseTestFilter
- displayName: TestFilter used for non-release type builds on non windows agents
+ displayName: TestFilter used for non-release type builds on non Windows agents
type: string
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
- name: nonWindowsIntegrationReleaseTestFilter
- displayName: TestFilter used for release type builds on non windows agents
+ displayName: TestFilter used for release type builds on non Windows agents
type: string
default: ' '
- name: isNightly
@@ -47,9 +51,7 @@ parameters:
default: false
variables:
- nodeVersion: 20
- dotnetVersion: 8.x
- dotnetIncludePreviewVersions: true
+ nodeVersion: 20.10.x
solution: umbraco.sln
buildConfiguration: Release
UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042
@@ -108,11 +110,9 @@ stages:
verbose: false
customCommand: run build
- task: UseDotNet@2
- displayName: Use .NET $(dotnetVersion)
+ displayName: Use .NET SDK from global.json
inputs:
- version: $(dotnetVersion)
- performMultiLevelLookup: true
- includePreviewVersions: $(dotnetIncludePreviewVersions)
+ useGlobalJson: true
- task: DotNetCoreCLI@2
displayName: Run dotnet restore
inputs:
@@ -256,16 +256,14 @@ stages:
artifact: build_output
path: $(Build.SourcesDirectory)
- task: UseDotNet@2
- displayName: Use .NET $(dotnetVersion)
+ displayName: Use .NET SDK from global.json
inputs:
- version: $(dotnetVersion)
- performMultiLevelLookup: true
- includePreviewVersions: $(dotnetIncludePreviewVersions)
+ useGlobalJson: true
- task: DotNetCoreCLI@2
displayName: Run dotnet test
inputs:
command: test
- projects: '**/*.Tests.UnitTests.csproj'
+ projects: 'tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj'
arguments: '--configuration $(buildConfiguration) --no-build'
testRunTitle: Unit Tests - $(Agent.OS)
@@ -288,46 +286,36 @@ stages:
vmImage: 'macOS-latest'
pool:
vmImage: $(vmImage)
+ variables:
+ Tests__Database__DatabaseType: 'Sqlite'
steps:
+ # Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
+
- task: UseDotNet@2
- displayName: Use .NET $(dotnetVersion)
+ displayName: Use .NET SDK from global.json
inputs:
- version: $(dotnetVersion)
- performMultiLevelLookup: true
- includePreviewVersions: $(dotnetIncludePreviewVersions)
+ useGlobalJson: true
+
+ # Test
- task: DotNetCoreCLI@2
- displayName: Run dotnet test Windows
- condition: eq(variables['Agent.OS'],'Windows_NT')
+ displayName: Run dotnet test
inputs:
command: test
- projects: '**/*.Tests.Integration.csproj'
+ projects: 'tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj'
testRunTitle: Integration Tests SQLite - $(Agent.OS)
- ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
+ ${{ if and(eq(variables['Agent.OS'],'Windows_NT'), or(variables.releaseTestFilter, parameters.forceReleaseTestFilter)) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
- ${{ else }}:
- arguments: '--configuration $(buildConfiguration) ${{parameters.integrationNonReleaseTestFilter}}'
- env:
- Tests__Database__DatabaseType: 'Sqlite'
- Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
- - task: DotNetCoreCLI@2
- displayName: Run dotnet test Non Windows
- condition: ne(variables['Agent.OS'],'Windows_NT')
- inputs:
- command: test
- projects: '**/*.Tests.Integration.csproj'
- testRunTitle: Integration Tests SQLite - $(Agent.OS)
- ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
+ ${{ elseif eq(variables['Agent.OS'],'Windows_NT') }}:
+ arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
+ ${{ elseif or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
${{ else }}:
- arguments: '--configuration $(buildConfiguration) ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
- env:
- Tests__Database__DatabaseType: 'Sqlite'
- Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
+ arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
# Integration Tests (SQL Server)
- job:
@@ -338,208 +326,363 @@ stages:
matrix:
Windows:
vmImage: 'windows-latest'
- testDb: LocalDb
- connectionString: N/A
+ Tests__Database__DatabaseType: LocalDb
+ Tests__Database__SQLServerMasterConnectionString: N/A
Linux:
vmImage: 'ubuntu-latest'
- testDb: SqlServer
- connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=true'
+ SA_PASSWORD: UmbracoIntegration123!
+ Tests__Database__DatabaseType: SqlServer
+ Tests__Database__SQLServerMasterConnectionString: 'Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
pool:
vmImage: $(vmImage)
- variables:
- SA_PASSWORD: UmbracoIntegration123!
steps:
+ # Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
+
- task: UseDotNet@2
- displayName: Use .NET $(dotnetVersion)
+ displayName: Use .NET SDK from global.json
inputs:
- version: $(dotnetVersion)
- includePreviewVersions: $(dotnetIncludePreviewVersions)
- - powershell: sqllocaldb start mssqllocaldb
- displayName: Start localdb (Windows only)
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
- - powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest
- displayName: Start SQL Server (Linux only)
+ useGlobalJson: true
+
+ # Start SQL Server
+ - powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
+ displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: SqlLocalDB start MSSQLLocalDB
+ displayName: Start SQL Server LocalDB (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+
+ # Test
- task: DotNetCoreCLI@2
- displayName: Run dotnet test Windows
- condition: eq(variables['Agent.OS'],'Windows_NT')
+ displayName: Run dotnet test
inputs:
command: test
- projects: '**/*.Tests.Integration.csproj'
+ projects: 'tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj'
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
- ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
+ ${{ if and(eq(variables['Agent.OS'],'Windows_NT'), or(variables.releaseTestFilter, parameters.forceReleaseTestFilter)) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
- ${{ else }}:
+ ${{ elseif eq(variables['Agent.OS'],'Windows_NT') }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
- env:
- Tests__Database__DatabaseType: $(testDb)
- Tests__Database__SQLServerMasterConnectionString: $(connectionString)
- Umbraco__CMS__Global__MainDomLock: 'SqlMainDomLock'
- - task: DotNetCoreCLI@2
- displayName: Run dotnet test NonWindows
- condition: ne(variables['Agent.OS'],'Windows_NT')
- inputs:
- command: test
- projects: '**/*.Tests.Integration.csproj'
- testRunTitle: Integration Tests SQL Server - $(Agent.OS)
- ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
+ ${{ elseif or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
${{ else }}:
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
- env:
- Tests__Database__DatabaseType: $(testDb)
- Tests__Database__SQLServerMasterConnectionString: $(connectionString)
- Umbraco__CMS__Global__MainDomLock: 'SqlMainDomLock'
+
+ # Stop SQL Server
+ - pwsh: docker stop mssql
+ displayName: Stop SQL Server Docker image (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: SqlLocalDB stop MSSQLLocalDB
+ displayName: Stop SQL Server LocalDB (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
- stage: E2E
- variables:
- npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
displayName: E2E Tests
dependsOn: Build
+ variables:
+ npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
+ # Enable console logging in Release mode
+ SERILOG__WRITETO__0__NAME: Async
+ SERILOG__WRITETO__0__ARGS__CONFIGURE__0__NAME: Console
+ # Set unattended install settings
+ UMBRACO__CMS__UNATTENDED__INSTALLUNATTENDED: true
+ UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERNAME: Playwright Test
+ UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD: UmbracoAcceptance123!
+ UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL: playwright@umbraco.com
+ # Custom Umbraco settings
+ UMBRACO__CMS__CONTENT__CONTENTVERSIONCLEANUPPOLICY__ENABLECLEANUP: false
+ UMBRACO__CMS__GLOBAL__DISABLEELECTIONFORSINGLESERVER: true
+ UMBRACO__CMS__GLOBAL__INSTALLMISSINGDATABASE: true
+ UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042
+ UMBRACO__CMS__GLOBAL__VERSIONCHECKPERIOD: 0
+ UMBRACO__CMS__GLOBAL__USEHTTPS: true
+ UMBRACO__CMS__HEALTHCHECKS__NOTIFICATION__ENABLED: false
+ UMBRACO__CMS__KEEPALIVE__DISABLEKEEPALIVETASK: true
+ UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL: https://localhost:44331/
+ ASPNETCORE_URLS: https://localhost:44331
jobs:
# E2E Tests
- job:
- displayName: E2E Tests
- timeoutInMinutes: 120
+ displayName: E2E Tests (SQLite)
variables:
- Umbraco__CMS__Unattended__UnattendedUserName: Playwright Test
- Umbraco__CMS__Unattended__UnattendedUserPassword: UmbracoAcceptance123!
- Umbraco__CMS__Unattended__UnattendedUserEmail: playwright@umbraco.com
- ASPNETCORE_URLS: https://localhost:8443
+ # Connection string
+ CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
+ CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
strategy:
matrix:
Linux:
vmImage: 'ubuntu-latest'
- dockerfile: umbraco-linux.docker
- dockerImageName: umbraco-linux
Windows:
vmImage: 'windows-latest'
- DOTNET_GENERATE_ASPNET_CERTIFICATE: true # Automatically generate HTTPS development certificate on Windows
- # Enable console logging in Release mode
- Serilog__WriteTo__0__Name: Async
- Serilog__WriteTo__0__Args__configure__0__Name: Console
- # Set unattended install settings
- Umbraco__CMS__Unattended__InstallUnattended: true
- Umbraco__CMS__Global__InstallMissingDatabase: true
- UmbracoDatabaseServer: (LocalDB)\MSSQLLocalDB
- UmbracoDatabaseName: AcceptanceTestDB
- ConnectionStrings__umbracoDbDSN: Server=$(UmbracoDatabaseServer);Database=$(UmbracoDatabaseName);Integrated Security=true;
- # Custom Umbraco settings
- Umbraco__CMS__Global__VersionCheckPeriod: 0
- Umbraco__CMS__Global__UseHttps: true
- Umbraco__CMS__HealthChecks__Notification__Enabled: false
- Umbraco__CMS__KeepAlive__DisableKeepAliveTask: true
pool:
vmImage: $(vmImage)
steps:
+ # Setup test environment
- task: DownloadPipelineArtifact@2
- displayName: Download nupkg
+ displayName: Download NuGet artifacts
inputs:
artifact: nupkg
- path: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/misc/nupkg
+ path: $(Agent.BuildDirectory)/app/nupkg
+
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- - task: Cache@2
- displayName: Cache node_modules
- inputs:
- key: '"npm_e2e" | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
- restoreKeys: |
- "npm_e2e" | "$(Agent.OS)"
- "npm_e2e"
- path: $(npm_config_cache)
- - pwsh: |
- New-Item -Path "." -Name ".env" -ItemType "file" -Value "UMBRACO_USER_LOGIN=$(Umbraco__CMS__Unattended__UnattendedUserEmail)
- UMBRACO_USER_PASSWORD=$(Umbraco__CMS__Unattended__UnattendedUserPassword)
- URL=$(ASPNETCORE_URLS)"
- displayName: Generate .env
- workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/
- - script: npm ci --no-fund --no-audit --prefer-offline
- workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/
- displayName: Run npm ci
- - pwsh: sqllocaldb start mssqllocaldb
- displayName: Start localdb (Windows only)
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
- - pwsh: Invoke-Sqlcmd -Query "CREATE DATABASE $env:UmbracoDatabaseName" -ServerInstance $env:UmbracoDatabaseServer
- displayName: Create database (Windows only)
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+
- task: UseDotNet@2
- displayName: Use .NET $(dotnetVersion)
+ displayName: Use .NET SDK from global.json
inputs:
- version: $(dotnetVersion)
- performMultiLevelLookup: true
- includePreviewVersions: $(dotnetIncludePreviewVersions)
+ useGlobalJson: true
+
- pwsh: |
- $sha = 'g$(Build.SourceVersion)'.substring(0, 8)
- docker build -t $(dockerImageName):$sha -f $(dockerfile) .
- mkdir -p $(Build.ArtifactStagingDirectory)/docker-images
- docker save -o $(Build.ArtifactStagingDirectory)/docker-images/$(dockerImageName).$sha.tar $(dockerImageName):$sha
- dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p $(Umbraco__CMS__Unattended__UnattendedUserPassword)
- docker run --name $(dockerImageName) -dp 8080:5000 -dp 8443:5001 -e UMBRACO__CMS__GLOBAL__ID=$(UMBRACO__CMS__GLOBAL__ID) -e ASPNETCORE_Kestrel__Certificates__Default__Password="$(Umbraco__CMS__Unattended__UnattendedUserPassword)" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v ${HOME}/.aspnet/https:/https/ $(dockerImageName):$sha
- docker ps
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- displayName: Build and run container (Linux only)
- workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
+ "UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
+ UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
+ URL=$(ASPNETCORE_URLS)" | Out-File .env
+ displayName: Generate .env
+ workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
+
+ # Cache and restore NPM packages
+ - task: Cache@2
+ displayName: Cache NPM packages
+ inputs:
+ key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
+ restoreKeys: |
+ npm_e2e | "$(Agent.OS)"
+ npm_e2e
+ path: $(npm_config_cache)
+
+ - script: npm ci --no-fund --no-audit --prefer-offline
+ workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
+ displayName: Restore NPM packages
+
+ # Build application
- pwsh: |
- dotnet new --install ./nupkg/Umbraco.Templates.*.nupkg
- dotnet new umbraco --name AcceptanceTestProject --no-restore --output .
- dotnet restore --configfile ./nuget.config
- dotnet build --configuration $(buildConfiguration) --no-restore
+ $cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
+ dotnet new nugetconfig
+ dotnet nuget add source ./nupkg --name Local
+ dotnet new install Umbraco.Templates::$cmsVersion
+ dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
+ dotnet restore UmbracoProject
+ cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
+ dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
dotnet dev-certs https
- $process = Start-Process -FilePath "dotnet" -ArgumentList "run --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
+ displayName: Build application
+ workingDirectory: $(Agent.BuildDirectory)/app
+
+ # Run application
+ - bash: |
+ nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
+ echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
+ displayName: Run application (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ workingDirectory: $(Agent.BuildDirectory)/app
+
+ - pwsh: |
+ $process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
+ displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
- displayName: Build and run app (Windows only)
- workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
+ workingDirectory: $(Agent.BuildDirectory)/app
+
+ # Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
- displayName: Wait for app
+ displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+
+ # Install Playwright and dependencies
- pwsh: npx playwright install --with-deps
displayName: Install Playwright
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+
+ # Test
- pwsh: npm run test --ignore-certificate-errors
- displayName: Run Playwright (Desktop)
+ displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
+
+ # Stop application
+ - bash: kill -15 $(AcceptanceTestProcessId)
+ displayName: Stop application (Linux)
+ condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
+ displayName: Stop application (Windows)
+ condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
+
+ # Copy artifacts
- pwsh: |
- docker logs $(dockerImageName) > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1
- docker stop $(dockerImageName)
- condition: eq(variables['Agent.OS'], 'Linux')
- displayName: Stop app (Linux only)
- - pwsh: Stop-Process $env:AcceptanceTestProcessId
- condition: eq(variables['Agent.OS'], 'Windows_NT')
- displayName: Stop app (Windows only)
- - task: PowerShell@2
- displayName: Check if artifacts folder exists
- inputs:
- targetType: inline
- script: |
- $MyVariable = Test-Path -Path $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/results
- Write-Host "##vso[task.setvariable variable=resultFolderExists;]$MyVariable"
- - task: CopyFiles@2
- displayName: Prepare artifacts
- condition: eq(variables.resultFolderExists, 'True')
- inputs:
- sourceFolder: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/results/
- targetFolder: $(Build.ArtifactStagingDirectory)/playwright
+ if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
+ Copy-Item tests/Umbraco.Tests.AcceptanceTest/results $(Build.ArtifactStagingDirectory) -Recurse
+ }
+ displayName: Copy Playwright results
+ condition: succeededOrFailed()
+
+ # Publish
- task: PublishPipelineArtifact@1
- condition: always()
displayName: Publish test artifacts
+ condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
- artifact: 'E2E artifacts - $(Agent.OS) - Attempt #$(System.JobAttempt)'
+ artifact: 'Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)'
+ - job:
+ displayName: E2E Tests (SQL Server)
+ condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerAcceptanceTests}})
+ variables:
+ # Connection string
+ CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
+ CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
+ strategy:
+ matrix:
+ Linux:
+ vmImage: 'ubuntu-latest'
+ SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
+ CONNECTIONSTRINGS__UMBRACODBDSN: 'Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
+ Windows:
+ vmImage: 'windows-latest'
+ pool:
+ vmImage: $(vmImage)
+ steps:
+ # Setup test environment
+ - task: DownloadPipelineArtifact@2
+ displayName: Download NuGet artifacts
+ inputs:
+ artifact: nupkg
+ path: $(Agent.BuildDirectory)/app/nupkg
+
+ - task: NodeTool@0
+ displayName: Use Node.js $(nodeVersion)
+ inputs:
+ versionSpec: $(nodeVersion)
+
+ - task: UseDotNet@2
+ displayName: Use .NET SDK from global.json
+ inputs:
+ useGlobalJson: true
+
+ - pwsh: |
+ "UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
+ UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
+ URL=$(ASPNETCORE_URLS)" | Out-File .env
+ displayName: Generate .env
+ workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
+
+ # Cache and restore NPM packages
+ - task: Cache@2
+ displayName: Cache NPM packages
+ inputs:
+ key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
+ restoreKeys: |
+ npm_e2e | "$(Agent.OS)"
+ npm_e2e
+ path: $(npm_config_cache)
+
+ - script: npm ci --no-fund --no-audit --prefer-offline
+ workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
+ displayName: Restore NPM packages
+
+ # Build application
+ - pwsh: |
+ $cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
+ dotnet new nugetconfig
+ dotnet nuget add source ./nupkg --name Local
+ dotnet new install Umbraco.Templates::$cmsVersion
+ dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
+ dotnet restore UmbracoProject
+ cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
+ dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
+ dotnet dev-certs https
+ displayName: Build application
+ workingDirectory: $(Agent.BuildDirectory)/app
+
+ # Start SQL Server
+ - powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
+ displayName: Start SQL Server Docker image (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: SqlLocalDB start MSSQLLocalDB
+ displayName: Start SQL Server LocalDB (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+
+ # Run application
+ - bash: |
+ nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
+ echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
+ displayName: Run application (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ workingDirectory: $(Agent.BuildDirectory)/app
+
+ - pwsh: |
+ $process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
+ Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
+ displayName: Run application (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+ workingDirectory: $(Agent.BuildDirectory)/app
+
+ # Wait for application to start responding to requests
+ - pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
+ displayName: Wait for application
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+
+ # Install Playwright and dependencies
+ - pwsh: npx playwright install --with-deps
+ displayName: Install Playwright
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+
+ # Test
+ - pwsh: npm run test --ignore-certificate-errors
+ displayName: Run Playwright tests
+ continueOnError: true
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+ env:
+ CI: true
+ CommitId: $(Build.SourceVersion)
+ AgentOs: $(Agent.OS)
+
+ # Stop application
+ - bash: kill -15 $(AcceptanceTestProcessId)
+ displayName: Stop application (Linux)
+ condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
+ displayName: Stop application (Windows)
+ condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
+
+ # Stop SQL Server
+ - pwsh: docker stop mssql
+ displayName: Stop SQL Server Docker image (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: SqlLocalDB stop MSSQLLocalDB
+ displayName: Stop SQL Server LocalDB (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+
+ # Copy artifacts
+ - pwsh: |
+ if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
+ Copy-Item tests/Umbraco.Tests.AcceptanceTest/results $(Build.ArtifactStagingDirectory) -Recurse
+ }
+ displayName: Copy Playwright results
+ condition: succeededOrFailed()
+
+ # Publish
+ - task: PublishPipelineArtifact@1
+ displayName: Publish test artifacts
+ condition: succeededOrFailed()
+ inputs:
+ targetPath: $(Build.ArtifactStagingDirectory)
+ artifact: 'Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)'
###############################################
## Release
@@ -599,7 +742,7 @@ stages:
pool:
vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
variables:
- umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
+ umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
displayName: Upload API Documention
dependsOn:
- Build
diff --git a/global.json b/global.json
index 36394634bd..391ba3c2a3 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,6 @@
{
"sdk": {
- "version": "8.0.0",
- "rollForward": "latestFeature",
- "allowPrerelease": false
+ "version": "8.0.100",
+ "rollForward": "latestFeature"
}
}
diff --git a/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SQLiteMemoryComposer.cs b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SQLiteMemoryComposer.cs
new file mode 100644
index 0000000000..63f330a564
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SQLiteMemoryComposer.cs
@@ -0,0 +1,42 @@
+using Microsoft.Data.Sqlite;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Extensions;
+
+namespace UmbracoProject;
+
+///
+/// Ensures a SQLite in-memory database is persisted for the whole application duration.
+///
+public sealed class SQLiteMemoryComposer : IComposer
+{
+ public void Compose(IUmbracoBuilder builder)
+ {
+ var connectionString = builder.Config.GetUmbracoConnectionString(out var providerName);
+ if (!string.IsNullOrEmpty(connectionString) &&
+ Constants.ProviderNames.SQLLite.InvariantEquals(providerName) &&
+ connectionString.InvariantContains("Mode=Memory"))
+ {
+ // Open new SQLite connection to ensure in-memory database is persisted for the whole application duration
+ var connection = new SqliteConnection(connectionString);
+ connection.Open();
+
+ // And ensure connection is kept open (by keeping a reference) and gets gracefully closed/disposed when application stops
+ builder.Services.AddHostedService(_ => new SQLiteMemoryHostedService(connection));
+ }
+ }
+
+ private sealed class SQLiteMemoryHostedService : IHostedService, IAsyncDisposable
+ {
+ private readonly SqliteConnection _connection;
+
+ public SQLiteMemoryHostedService(SqliteConnection connection) => _connection = connection;
+
+ public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+ public async Task StopAsync(CancellationToken cancellationToken) => await _connection.CloseAsync();
+
+ public async ValueTask DisposeAsync() => await _connection.DisposeAsync();
+ }
+}
diff --git a/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SqlServerDelayedDurabilityComposer.cs b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SqlServerDelayedDurabilityComposer.cs
new file mode 100644
index 0000000000..956a101fbc
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SqlServerDelayedDurabilityComposer.cs
@@ -0,0 +1,44 @@
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.Events;
+using Umbraco.Cms.Core.Notifications;
+using Umbraco.Extensions;
+
+namespace UmbracoProject;
+
+///
+/// Disable waiting on log IO to finish when commiting a transaction (we can tolerate some data loss) on SQL Server.
+///
+public sealed class SqlServerDelayedDurabilityComposer : IComposer
+{
+ public void Compose(IUmbracoBuilder builder)
+ {
+ var connectionString = builder.Config.GetUmbracoConnectionString(out var providerName);
+ if (!string.IsNullOrEmpty(connectionString) &&
+ Constants.ProviderNames.SQLServer.InvariantEquals(providerName))
+ {
+ builder.AddNotificationAsyncHandler();
+ }
+ }
+
+ private sealed class SqlServerDelayedDurabilityInstallNotification : INotificationAsyncHandler
+ {
+ private readonly IOptions _connectionStrings;
+
+ public SqlServerDelayedDurabilityInstallNotification(IOptions connectionStrings) => _connectionStrings = connectionStrings;
+
+ public async Task HandleAsync(UnattendedInstallNotification notification, CancellationToken cancellationToken)
+ {
+ using var connection = new SqlConnection(_connectionStrings.Value.ConnectionString);
+ await connection.OpenAsync(cancellationToken);
+
+ // Disable waiting on log IO to finish when commiting a transaction (we can tolerate some data loss)
+ var command = new SqlCommand("ALTER DATABASE CURRENT SET DELAYED_DURABILITY = FORCED;", connection);
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+}
diff --git a/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SuspendScheduledPublishingComposer.cs b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SuspendScheduledPublishingComposer.cs
new file mode 100644
index 0000000000..ba9c220740
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/SuspendScheduledPublishingComposer.cs
@@ -0,0 +1,13 @@
+using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Infrastructure;
+
+namespace UmbracoProject;
+
+///
+/// Suspends/disables scheduled publishing, because that takes an eager write lock every minute, resulting in flaky test runs on SQLite.
+///
+public sealed class SuspendScheduledPublishingComposer : IComposer
+{
+ public void Compose(IUmbracoBuilder builder) => Suspendable.ScheduledPublishing.Suspend();
+}
diff --git a/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/Umbraco.Tests.AcceptanceTest.UmbracoProject.csproj b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/Umbraco.Tests.AcceptanceTest.UmbracoProject.csproj
new file mode 100644
index 0000000000..ac71f98e29
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/Umbraco.Tests.AcceptanceTest.UmbracoProject.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Library
+ UmbracoProject
+
+
+
+
+
diff --git a/tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Build.props b/tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Build.props
deleted file mode 100644
index 33a5cccb0b..0000000000
--- a/tests/Umbraco.Tests.AcceptanceTest/misc/Directory.Build.props
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/misc/nuget.config b/tests/Umbraco.Tests.AcceptanceTest/misc/nuget.config
deleted file mode 100644
index 08b6b81193..0000000000
--- a/tests/Umbraco.Tests.AcceptanceTest/misc/nuget.config
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker b/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker
deleted file mode 100644
index fda51841a6..0000000000
--- a/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker
+++ /dev/null
@@ -1,51 +0,0 @@
-############################################
-## Build
-############################################
-
-FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0.100-rc.1-jammy AS build
-
-COPY nuget.config .
-
-COPY nuget.config .
-
-WORKDIR /nupkg
-COPY nupkg .
-
-WORKDIR /build
-RUN dotnet new --install /nupkg/Umbraco.Templates.*.nupkg
-RUN dotnet new umbraco --name AcceptanceTestProject --no-restore --output .
-RUN dotnet restore --configfile /nuget.config
-RUN dotnet build --configuration Release --no-restore
-RUN dotnet publish --configuration Release --no-build --output /dist
-
-############################################
-## Run
-############################################
-
-FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0.0-rc.1-jammy AS run
-
-WORKDIR /app
-COPY --from=build dist .
-
-# Enable console logging in Release mode
-ENV Serilog__WriteTo__0__Name=Async
-ENV Serilog__WriteTo__0__Args__configure__0__Name=Console
-
-# Set unattended install settings
-ENV ConnectionStrings__umbracoDbDSN_ProviderName="Microsoft.Data.Sqlite"
-ENV ConnectionStrings__umbracoDbDSN="Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True"
-ENV Umbraco__CMS__Unattended__InstallUnattended="true"
-ENV Umbraco__CMS__Unattended__UnattendedUserName="Playwright Test"
-ENV Umbraco__CMS__Unattended__UnattendedUserEmail="playwright@umbraco.com"
-ENV Umbraco__CMS__Unattended__UnattendedUserPassword="UmbracoAcceptance123!"
-
-# Custom Umbraco settings
-ENV Umbraco__CMS__Global__VersionCheckPeriod="0"
-ENV Umbraco__CMS__Global__UseHttps="true"
-ENV Umbraco__CMS__HealthChecks__Notification__Enabled="false"
-ENV Umbraco__CMS__KeepAlive__DisableKeepAliveTask="true"
-
-# Set application URL
-ENV ASPNETCORE_URLS="http://0.0.0.0:5000;https://0.0.0.0:5001"
-
-CMD dotnet AcceptanceTestProject.dll
diff --git a/umbraco.sln b/umbraco.sln
index 517b3959e9..3742753fe9 100644
--- a/umbraco.sln
+++ b/umbraco.sln
@@ -133,14 +133,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
icon.png = icon.png
LICENSE.md = LICENSE.md
- umbraco.sln.DotSettings = umbraco.sln.DotSettings
nuget.config = nuget.config
+ umbraco.sln.DotSettings = umbraco.sln.DotSettings
version.json = version.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{20CE9C97-9314-4A19-BCF1-D12CF49B7205}"
ProjectSection(SolutionItems) = preProject
build\azure-pipelines.yml = build\azure-pipelines.yml
+ build\nightly-build-trigger.yml = build\nightly-build-trigger.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "csharp-docs", "csharp-docs", "{F2BF84D9-0A14-40AF-A0F3-B9BBBBC16A44}"
@@ -184,6 +185,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Persistence.EFC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Persistence.EFCore.SqlServer", "src\Umbraco.Cms.Persistence.EFCore.SqlServer\Umbraco.Cms.Persistence.EFCore.SqlServer.csproj", "{9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.AcceptanceTest.UmbracoProject", "tests\Umbraco.Tests.AcceptanceTest.UmbracoProject\Umbraco.Tests.AcceptanceTest.UmbracoProject.csproj", "{A13FF0A0-69FA-468A-9F79-565401D5C341}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -357,6 +360,12 @@ Global
{9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}.Release|Any CPU.Build.0 = Release|Any CPU
{9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU
{9276C3F0-0DC9-46C9-BF32-9EE79D92AE02}.SkipTests|Any CPU.Build.0 = Debug|Any CPU
+ {A13FF0A0-69FA-468A-9F79-565401D5C341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A13FF0A0-69FA-468A-9F79-565401D5C341}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A13FF0A0-69FA-468A-9F79-565401D5C341}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A13FF0A0-69FA-468A-9F79-565401D5C341}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A13FF0A0-69FA-468A-9F79-565401D5C341}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU
+ {A13FF0A0-69FA-468A-9F79-565401D5C341}.SkipTests|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -375,6 +384,7 @@ Global
{2B47AD9F-FFF1-448A-88F1-D4F568811738} = {F2BF84D9-0A14-40AF-A0F3-B9BBBBC16A44}
{25AECCB5-B187-4406-844B-91B8FF0FCB37} = {2B47AD9F-FFF1-448A-88F1-D4F568811738}
{EA628ABD-624E-4AF3-B548-6710D4D66531} = {2B47AD9F-FFF1-448A-88F1-D4F568811738}
+ {A13FF0A0-69FA-468A-9F79-565401D5C341} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC}