From 4498058343d736750dfb5fb142ccbb8116380636 Mon Sep 17 00:00:00 2001
From: Hao Kung <HaoK@users.noreply.github.com>
Date: Wed, 26 Feb 2020 12:55:41 -0800
Subject: [PATCH] [Helix] Shared framework support + Templates tests (#19177)

---
 .azure/pipelines/ci.yml                       | 17 ++++--
 .azure/pipelines/quarantined-tests.yml        |  5 +-
 eng/helix/content/InstallAppRuntime.ps1       | 54 +++++++++++++++++++
 eng/helix/content/InstallNode.ps1             |  4 +-
 eng/helix/content/installappruntime.sh        | 20 +++++++
 eng/helix/content/runtests.cmd                | 15 ++++++
 eng/helix/content/runtests.sh                 | 15 ++++++
 eng/targets/Helix.props                       |  2 +
 eng/targets/Helix.targets                     | 14 +++++
 .../test/BlazorServerTemplateTest.cs          |  6 ++-
 .../test/ByteOrderMarkTest.cs                 |  7 ++-
 .../test/EmptyWebTemplateTest.cs              |  4 +-
 src/ProjectTemplates/test/GrpcTemplateTest.cs |  4 +-
 .../test/Helpers/ProcessEx.cs                 | 13 +++--
 src/ProjectTemplates/test/Helpers/Project.cs  | 27 +++++-----
 .../test/Helpers/ProjectFactoryFixture.cs     |  6 ++-
 .../test/Helpers/TemplatePackageInstaller.cs  | 26 ++++++---
 .../test/IdentityUIPackageTest.cs             |  4 +-
 src/ProjectTemplates/test/MvcTemplateTest.cs  |  9 ++--
 .../test/ProjectTemplates.Tests.csproj        |  9 ++--
 .../test/RazorClassLibraryTemplateTest.cs     |  4 +-
 .../test/RazorPagesTemplateTest.cs            |  9 ++--
 .../SpaTemplateTest/AngularTemplateTest.cs    |  9 ++--
 .../SpaTemplateTest/ReactReduxTemplateTest.cs |  4 +-
 .../test/SpaTemplateTest/ReactTemplateTest.cs |  9 ++--
 .../test/WebApiTemplateTest.cs                |  4 +-
 .../test/WorkerTemplateTest.cs                |  4 +-
 .../E2ETesting/SeleniumStandaloneServer.cs    |  7 +++
 28 files changed, 248 insertions(+), 63 deletions(-)
 create mode 100644 eng/helix/content/InstallAppRuntime.ps1
 create mode 100644 eng/helix/content/installappruntime.sh

diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml
index 6f20958002f..82cb6cb5bce 100644
--- a/.azure/pipelines/ci.yml
+++ b/.azure/pipelines/ci.yml
@@ -154,8 +154,7 @@ stages:
         displayName: Build x64
 
       # Build the x86 shared framework
-      # TODO: make it possible to build for one Windows architecture at a time
-      # This is going to actually build x86 native assets. See https://github.com/dotnet/aspnetcore/issues/7196
+      # This is going to actually build x86 native assets.
       - script: ./build.cmd
                 -ci
                 -arch x86
@@ -631,7 +630,7 @@ stages:
         publishOnError: true
         includeForks: true
 
-# Helix x64
+  # Helix x64
   - template: jobs/default-build.yml
     parameters:
       condition: eq(variables['Build.Reason'], 'PullRequest')
@@ -640,9 +639,12 @@ stages:
       agentOs: Windows
       timeoutInMinutes: 180
       steps:
+      # Build the shared framework
+      - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.build.x64.binlog
+        displayName: Build shared fx
       - script: .\restore.cmd -ci
         displayName: Restore
-      - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
+      - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
         displayName: Run build.cmd helix target
         env:
           HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues
@@ -661,9 +663,13 @@ stages:
       agentOs: Windows
       timeoutInMinutes: 180
       steps:
+      # Build the shared framework
+      - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.daily.build.x64.binlog
+        displayName: Build shared fx
+      # Build the x86 shared framework
       - script: .\restore.cmd -ci
         displayName: Restore
-      - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsHelixJob=true /p:IsHelixDaily=true /p:BuildAllProjects=true /p:BuildNative=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
+      - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsHelixJob=true /p:IsHelixDaily=true /p:BuildAllProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
         displayName: Run build.cmd helix target
         env:
           HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues
@@ -683,6 +689,7 @@ stages:
       agentOs: Linux
       timeoutInMinutes: 180
       steps:
+      # Build the shared framework
       - script: ./restore.sh -ci
         displayName: Restore
       - script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:IsHelixDaily=true /p:BuildAllProjects=true /p:BuildNative=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
diff --git a/.azure/pipelines/quarantined-tests.yml b/.azure/pipelines/quarantined-tests.yml
index 6ac0143ed1e..bb9224b38b9 100644
--- a/.azure/pipelines/quarantined-tests.yml
+++ b/.azure/pipelines/quarantined-tests.yml
@@ -30,9 +30,12 @@ jobs:
     agentOs: Windows
     timeoutInMinutes: 240
     steps:
+    # Build the shared framework
+    - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.build.x64.binlog
+      displayName: Build shared fx    
     - script: .\restore.cmd -ci
       displayName: Restore
-    - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
+    - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl
       displayName: Run build.cmd helix target
       env:
         HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues
diff --git a/eng/helix/content/InstallAppRuntime.ps1 b/eng/helix/content/InstallAppRuntime.ps1
new file mode 100644
index 00000000000..9d9aaffb5c8
--- /dev/null
+++ b/eng/helix/content/InstallAppRuntime.ps1
@@ -0,0 +1,54 @@
+ <# 
+ .SYNOPSIS 
+     Installs an AspNetCore shared framework on a machine
+ .DESCRIPTION 
+     This script installs an AspNetCore shared framework on a machine
+ .PARAMETER AppRuntimePath
+     The path to the app runtime package to install.
+ .PARAMETER InstallDir
+     The directory to install the shared framework to.     
+ .PARAMETER Framework
+     The framework directory to copy the shared framework from.
+ .PARAMETER RuntimeIdentifier
+     The runtime identifier for the shared framework.
+ #> 
+param(
+    [Parameter(Mandatory = $true)]
+    $AppRuntimePath,
+    
+    [Parameter(Mandatory = $true)]
+    $InstallDir,
+
+    [Parameter(Mandatory = $true)]
+    $Framework,
+    
+    [Parameter(Mandatory = $true)]
+    $RuntimeIdentifier)
+
+$ErrorActionPreference = 'Stop'
+$ProgressPreference = 'SilentlyContinue' # Workaround PowerShell/PowerShell#2138
+
+Set-StrictMode -Version 1
+
+Write-Host "Extracting to $InstallDir"
+
+$zipPackage = [io.path]::ChangeExtension($AppRuntimePath, ".zip")
+Write-Host "Renaming to $zipPackage"
+Rename-Item -Path $AppRuntimePath -NewName $zipPackage
+if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) {
+    # Use built-in commands where possible as they are cross-plat compatible
+    Microsoft.PowerShell.Archive\Expand-Archive -Path $zipPackage -DestinationPath ".\tmpRuntime" -Force
+}
+else {
+    Remove-Item ".\tmpRuntime" -Recurse -ErrorAction Ignore
+    # Fallback to old approach for old installations of PowerShell
+    Add-Type -AssemblyName System.IO.Compression.FileSystem
+    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPackage, ".\tmpRuntime")
+}
+
+Get-ChildItem -Path ".\tmpRuntime" -Recurse
+
+Write-Host "Copying managed files to $InstallDir"
+Copy-Item -Path ".\tmpRuntime\runtimes\$RuntimeIdentifier\lib\$Framework\*" $InstallDir
+Write-Host "Copying native files to $InstallDir"
+Copy-Item -Path ".\tmpRuntime\runtimes\$RuntimeIdentifier\native\*" $InstallDir
diff --git a/eng/helix/content/InstallNode.ps1 b/eng/helix/content/InstallNode.ps1
index 84425f271b6..3754eee5f55 100644
--- a/eng/helix/content/InstallNode.ps1
+++ b/eng/helix/content/InstallNode.ps1
@@ -29,9 +29,9 @@ if (Get-Command "node.exe" -ErrorAction SilentlyContinue)
     exit
 }
 
-if (Test-Path "$output_dir\node.exe")
+if (Test-Path "$InstallDir\node.exe")
 {
-    Write-Host "Node.exe found at $output_dir"
+    Write-Host "Node.exe found at $InstallDir"
     exit
 }
 
diff --git a/eng/helix/content/installappruntime.sh b/eng/helix/content/installappruntime.sh
new file mode 100644
index 00000000000..45cb1554fab
--- /dev/null
+++ b/eng/helix/content/installappruntime.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+# Cause the script to fail if any subcommand fails
+set -e
+
+appRuntimePath=$1
+output_dir=$2
+framework=$3
+rid=$4
+tmpDir=./tmpRuntime
+
+echo "Installing shared framework from $appRuntimePath"
+cp $appRuntimePath sharedFx.zip
+
+mkdir -p $tmpDir
+unzip sharedFx.zip -d $tmpDir
+mkdir -p $output_dir
+echo "Copying to $output_dir"
+cp $tmpDir/runtimes/$rid/lib/$framework/* $output_dir
+cp $tmpDir/runtimes/$rid/native/* $output_dir
diff --git a/eng/helix/content/runtests.cmd b/eng/helix/content/runtests.cmd
index 80f5a66bb3b..73cf658c18c 100644
--- a/eng/helix/content/runtests.cmd
+++ b/eng/helix/content/runtests.cmd
@@ -21,7 +21,22 @@ set PATH=%DOTNET_ROOT%;%PATH%;%HELIX_CORRELATION_PAYLOAD%\node\bin
 powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'))) -Architecture %$arch% -Version %$sdkVersion% -InstallDir %DOTNET_ROOT%"
 powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'))) -Architecture %$arch% -Runtime dotnet -Version %$runtimeVersion% -InstallDir %DOTNET_ROOT%"
 
+if EXIST ".\Microsoft.AspNetCore.App" (
+    echo "Found Microsoft.AspNetCore.App, copying to %DOTNET_ROOT%\shared\Microsoft.AspNetCore.App\%runtimeVersion%"
+    xcopy /i /y ".\Microsoft.AspNetCore.App" %DOTNET_ROOT%\shared\Microsoft.AspNetCore.App\%runtimeVersion%\
+)
+
+echo "Current Directory: %HELIX_WORKITEM_ROOT%"
 set HELIX=%$helixQueue%
+set HELIX_DIR=%HELIX_WORKITEM_ROOT%
+set NUGET_FALLBACK_PACKAGES=%HELIX_DIR%
+set NUGET_RESTORE=%HELIX_DIR%\nugetRestore
+echo "Setting HELIX_DIR: %HELIX_DIR%"
+echo Creating nuget restore directory: %NUGET_RESTORE%
+mkdir %NUGET_RESTORE%
+mkdir logs
+
+dir
 
 %DOTNET_ROOT%\dotnet vstest %$target% -lt >discovered.txt
 find /c "Exception thrown" discovered.txt
diff --git a/eng/helix/content/runtests.sh b/eng/helix/content/runtests.sh
index 51b596afa07..c31297d587a 100644
--- a/eng/helix/content/runtests.sh
+++ b/eng/helix/content/runtests.sh
@@ -31,6 +31,14 @@ export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
 
 # Used by SkipOnHelix attribute
 export helix="$helix_queue_name"
+export HELIX_DIR="$DIR"
+export NUGET_FALLBACK_PACKAGES="$DIR"
+export NUGET_RESTORE="$DIR/nugetRestore"
+echo "Creating nugetRestore directory: $NUGET_RESTORE"
+mkdir $NUGET_RESTORE
+mkdir logs
+
+ls -la
 
 RESET="\033[0m"
 RED="\033[0;31m"
@@ -82,6 +90,13 @@ if [ $? -ne 0 ]; then
     done
 fi
 
+# Copy over any local shared fx if found
+if [ -d "Microsoft.AspNetCore.App" ]
+then
+    echo "Found Microsoft.AspNetCore.App directory, copying to $DOTNET_ROOT/shared/Microsoft.AspNetCore.App/$dotnet_runtime_version."
+    cp -r Microsoft.AspNetCore.App $DOTNET_ROOT/shared/Microsoft.AspNetCore.App/$dotnet_runtime_version
+fi
+
 if [ -e /proc/self/coredump_filter ]; then
   # Include memory in private and shared file-backed mappings in the dump.
   # This ensures that we can see disassembly from our shared libraries when
diff --git a/eng/targets/Helix.props b/eng/targets/Helix.props
index d36c4a1a7aa..d4c7a99d524 100644
--- a/eng/targets/Helix.props
+++ b/eng/targets/Helix.props
@@ -21,6 +21,8 @@
     <HelixUseArchive>false</HelixUseArchive>
     <LoggingTestingDisableFileLogging Condition="'$(IsHelixJob)' == 'true'">false</LoggingTestingDisableFileLogging>
     <NodeVersion>10.15.3</NodeVersion>
+    <AppRuntimeVersion>5.0.0-ci</AppRuntimeVersion>
+    <TestDependsOnAspNetRuntime>false</TestDependsOnAspNetRuntime>
   </PropertyGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
diff --git a/eng/targets/Helix.targets b/eng/targets/Helix.targets
index 722158b18f4..00849843534 100644
--- a/eng/targets/Helix.targets
+++ b/eng/targets/Helix.targets
@@ -16,6 +16,20 @@
     <HelixPreCommand Include="call RunPowershell.cmd InstallNode.ps1 $(NodeVersion) %25HELIX_CORRELATION_PAYLOAD%25\node\bin || exit /b 1" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(IsHelixJob)' == 'true' AND '$(IsWindowsHelixQueue)' == 'true' AND '$(TestDependsOnAspNetRuntime)' == 'true'">
+    <HelixContent Include="$(RepoRoot)artifacts\packages\Release\Shipping\Microsoft.AspNetCore.App.Runtime.win-x64.$(AppRuntimeVersion).nupkg" />
+    <HelixPreCommand Include="call RunPowershell.cmd InstallAppRuntime.ps1 Microsoft.AspNetCore.App.Runtime.win-x64.$(AppRuntimeVersion).nupkg Microsoft.AspNetCore.App netcoreapp5.0 win-x64 || exit /b 1" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(IsHelixJob)' == 'true' AND '$(IsWindowsHelixQueue)' == 'false' AND '$(TestDependsOnAspNetRuntime)' == 'true'">
+    <HelixContent Include="$(RepoRoot)artifacts\packages\Release\Shipping\Microsoft.AspNetCore.App.Runtime.win-x64.$(AppRuntimeVersion).nupkg" />
+    <HelixPreCommand Include="./installappruntime.sh Microsoft.AspNetCore.App.Runtime.win-x64.$(AppRuntimeVersion).nupkg Microsoft.AspNetCore.App netcoreapp5.0 win-x64" />
+  </ItemGroup>
+  
+  <ItemGroup Condition="'$(IsHelixJob)' == 'true' AND '$(TestDependsOnAspNetRuntime)' == 'true'">
+    <HelixContent Include="$(RepoRoot)artifacts\packages\Release\Shipping\*-ci.nupkg" />
+  </ItemGroup>  
+  
   <!-- Item group has to be defined here becasue Helix.props is evaluated before xunit.runner.console.props  -->
   <ItemGroup Condition="$(BuildHelixPayload)">
     <Content Include="@(HelixContent)" />
diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs
index 1c6239d10ed..80201023862 100644
--- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs
+++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs
@@ -24,7 +24,8 @@ namespace Templates.Test
 
         public Project Project { get; private set; }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public async Task BlazorServerTemplateWorks_NoAuth()
         {
             Project = await ProjectFactory.GetOrCreateProject("blazorservernoauth", Output);
@@ -79,9 +80,10 @@ namespace Templates.Test
             }
         }
 
-        [Theory]
+        [ConditionalTheory]
         [InlineData(true)]
         [InlineData(false)]
+        [SkipOnHelix("ef restore no worky")]
         public async Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB)
         {
             Project = await ProjectFactory.GetOrCreateProject("blazorserverindividual" + (useLocalDB ? "uld" : ""), Output);
diff --git a/src/ProjectTemplates/test/ByteOrderMarkTest.cs b/src/ProjectTemplates/test/ByteOrderMarkTest.cs
index df7acd2f4dd..76600bf9e28 100644
--- a/src/ProjectTemplates/test/ByteOrderMarkTest.cs
+++ b/src/ProjectTemplates/test/ByteOrderMarkTest.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
+using Microsoft.AspNetCore.Testing;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -19,7 +20,8 @@ namespace Templates.Test
             _output = output;
         }
 
-        [Theory]
+        [ConditionalTheory]
+        [SkipOnHelix("missing files")]
         [InlineData("Web.ProjectTemplates")]
         [InlineData("Web.Spa.ProjectTemplates")]
         public void JSAndJSONInAllTemplates_ShouldNotContainBOM(string projectName)
@@ -60,7 +62,8 @@ namespace Templates.Test
             Assert.False(filesWithBOMCharactersPresent);
         }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("missing files")]
         public void RazorFilesInWebProjects_ShouldContainBOM()
         {
             var projectName = "Web.ProjectTemplates";
diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs
index bcd52da1d59..a448f039a4d 100644
--- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs
+++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs
@@ -3,6 +3,7 @@
 
 using System.Threading.Tasks;
 using Templates.Test.Helpers;
+using Microsoft.AspNetCore.Testing;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -22,7 +23,8 @@ namespace Templates.Test
 
         public ITestOutputHelper Output { get; }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
         public async Task EmptyWebTemplateCSharp()
         {
             await EmtpyTemplateCore(languageOverride: null);
diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs
index 8d0eef03957..eda3fb44ef0 100644
--- a/src/ProjectTemplates/test/GrpcTemplateTest.cs
+++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs
@@ -4,6 +4,7 @@
 using System;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Testing;
 using Templates.Test.Helpers;
 using Xunit;
 using Xunit.Abstractions;
@@ -23,7 +24,8 @@ namespace Templates.Test
         public ProjectFactoryFixture ProjectFactory { get; }
         public ITestOutputHelper Output { get; }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("Not supported queues", Queues = "Windows.7.Amd64;Windows.7.Amd64.Open;OSX.1014.Amd64;OSX.1014.Amd64.Open")]
         public async Task GrpcTemplate()
         {
             Project = await ProjectFactory.GetOrCreateProject("grpc", Output);
diff --git a/src/ProjectTemplates/test/Helpers/ProcessEx.cs b/src/ProjectTemplates/test/Helpers/ProcessEx.cs
index f5bf857996d..db132bdafe0 100644
--- a/src/ProjectTemplates/test/Helpers/ProcessEx.cs
+++ b/src/ProjectTemplates/test/Helpers/ProcessEx.cs
@@ -5,6 +5,7 @@ using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.InteropServices;
@@ -99,6 +100,11 @@ namespace Templates.Test.Helpers
             }
 
             startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES;
+            
+            if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix")))
+            {
+                startInfo.EnvironmentVariables["NUGET_FALLBACK_PACKAGES"] = Environment.GetEnvironmentVariable("NUGET_FALLBACK_PACKAGES");
+            }
 
             output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]");
             var proc = Process.Start(startInfo);
@@ -189,11 +195,12 @@ namespace Templates.Test.Helpers
             }
         }
 
-        private static string GetNugetPackagesRestorePath() =>
-            typeof(ProcessEx).Assembly
+        private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE"))) 
+            ? typeof(ProcessEx).Assembly
                 .GetCustomAttributes<AssemblyMetadataAttribute>()
                 .First(attribute => attribute.Key == "TestPackageRestorePath")
-                .Value;
+                .Value
+            : Environment.GetEnvironmentVariable("NUGET_RESTORE");
 
         public void Dispose()
         {
diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs
index 33e50d20915..28a9bdd9170 100644
--- a/src/ProjectTemplates/test/Helpers/Project.cs
+++ b/src/ProjectTemplates/test/Helpers/Project.cs
@@ -24,7 +24,16 @@ namespace Templates.Test.Helpers
         public static bool IsCIEnvironment => typeof(Project).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
             .Any(a => a.Key == "ContinuousIntegrationBuild");
 
-        public static string ArtifactsLogDir => GetAssemblyMetadata("ArtifactsLogDir");
+        public static string ArtifactsLogDir => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DIR"))) 
+            ? GetAssemblyMetadata("ArtifactsLogDir")
+            : Path.Combine(Environment.GetEnvironmentVariable("HELIX_DIR"), "logs");
+        
+        // FIGURE OUT EF PATH
+        public static string DotNetEfFullPath => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) 
+            ? typeof(ProjectFactoryFixture).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
+                .First(attribute => attribute.Key == "DotNetEfFullPath")
+                .Value
+            : Path.Combine("NuGetPackageRoot", "dotnet-ef/$(DotnetEfPackageVersion)/tools/netcoreapp3.1/any/dotnet-ef.dll");
 
         public SemaphoreSlim DotNetNewLock { get; set; }
         public SemaphoreSlim NodeLock { get; set; }
@@ -297,13 +306,7 @@ namespace Templates.Test.Helpers
 
         internal async Task<ProcessEx> RunDotNetEfCreateMigrationAsync(string migrationName)
         {
-            var assembly = typeof(ProjectFactoryFixture).Assembly;
-
-            var dotNetEfFullPath = assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
-                .First(attribute => attribute.Key == "DotNetEfFullPath")
-                .Value;
-
-            var args = $"\"{dotNetEfFullPath}\" --verbose --no-build migrations add {migrationName}";
+            var args = $"\"{DotNetEfFullPath}\" --verbose --no-build migrations add {migrationName}";
 
             // Only run one instance of 'dotnet new' at once, as a workaround for
             // https://github.com/aspnet/templating/issues/63
@@ -322,13 +325,7 @@ namespace Templates.Test.Helpers
 
         internal async Task<ProcessEx> RunDotNetEfUpdateDatabaseAsync()
         {
-            var assembly = typeof(ProjectFactoryFixture).Assembly;
-
-            var dotNetEfFullPath = assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
-                .First(attribute => attribute.Key == "DotNetEfFullPath")
-                .Value;
-
-            var args = $"\"{dotNetEfFullPath}\" --verbose --no-build database update";
+            var args = $"\"{DotNetEfFullPath}\" --verbose --no-build database update";
 
             // Only run one instance of 'dotnet new' at once, as a workaround for
             // https://github.com/aspnet/templating/issues/63
diff --git a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs
index ffd1b0ae4ff..9399433be96 100644
--- a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs
+++ b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs
@@ -61,9 +61,11 @@ namespace Templates.Test.Helpers
         }
 
         private static string GetTemplateFolderBasePath(Assembly assembly) =>
-            assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
+            (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DIR"))) 
+            ? assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
                 .Single(a => a.Key == "TestTemplateCreationFolder")
-                .Value;
+                .Value
+            : Path.Combine(Environment.GetEnvironmentVariable("HELIX_DIR"), "Templates", "BaseFolder");
 
         public void Dispose()
         {
diff --git a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs
index 07db888279a..3eed004ef60 100644
--- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs
+++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs
@@ -42,10 +42,12 @@ namespace Templates.Test.Helpers
             "Microsoft.AspNetCore.Blazor.Templates",
         };
 
-        public static string CustomHivePath { get; } = typeof(TemplatePackageInstaller)
-            .Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
-            .Single(s => s.Key == "CustomTemplateHivePath").Value;
-
+        public static string CustomHivePath { get; } = (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) 
+                    ? typeof(TemplatePackageInstaller)
+                        .Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
+                        .Single(s => s.Key == "CustomTemplateHivePath").Value
+                    : Path.Combine("Hives", ".templateEngine");
+                                        
         public static async Task EnsureTemplatingEngineInitializedAsync(ITestOutputHelper output)
         {
             await InstallerLock.WaitAsync();
@@ -81,11 +83,19 @@ namespace Templates.Test.Helpers
 
         private static async Task InstallTemplatePackages(ITestOutputHelper output)
         {
-            var builtPackages = Directory.EnumerateFiles(
-                    typeof(TemplatePackageInstaller).Assembly
+            string packagesDir;
+            if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix")))
+            {
+                packagesDir = ".";
+            }
+            else
+            {
+                packagesDir = typeof(TemplatePackageInstaller).Assembly
                     .GetCustomAttributes<AssemblyMetadataAttribute>()
-                    .Single(a => a.Key == "ArtifactsShippingPackagesDir").Value,
-                    "*.nupkg")
+                    .Single(a => a.Key == "ArtifactsShippingPackagesDir").Value;
+            }
+
+            var builtPackages = Directory.EnumerateFiles(packagesDir, "*Templates*.nupkg")
                 .Where(p => _templatePackages.Any(t => Path.GetFileName(p).StartsWith(t, StringComparison.OrdinalIgnoreCase)))
                 .ToArray();
 
diff --git a/src/ProjectTemplates/test/IdentityUIPackageTest.cs b/src/ProjectTemplates/test/IdentityUIPackageTest.cs
index deede64521a..fe794a3629b 100644
--- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs
+++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Net;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Testing;
 using Templates.Test.Helpers;
 using Xunit;
 using Xunit.Abstractions;
@@ -117,8 +118,9 @@ namespace Templates.Test
             "Identity/lib/jquery-validation-unobtrusive/LICENSE.txt",
         };
 
-        [Theory]
+        [ConditionalTheory]
         [MemberData(nameof(MSBuildIdentityUIPackageOptions))]
+        [SkipOnHelix("ef restore no worky")]
         public async Task IdentityUIPackage_WorksWithDifferentOptions(IDictionary<string, string> packageOptions, string versionValidator, string[] expectedFiles)
         {
             Project = await ProjectFactory.GetOrCreateProject("identityuipackage" + string.Concat(packageOptions.Values), Output);
diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs
index 976eef53463..271aed575b2 100644
--- a/src/ProjectTemplates/test/MvcTemplateTest.cs
+++ b/src/ProjectTemplates/test/MvcTemplateTest.cs
@@ -28,7 +28,8 @@ namespace Templates.Test
         [Fact]
         public async Task MvcTemplate_NoAuthFSharp() => await MvcTemplateCore(languageOverride: "F#");
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
         public async Task MvcTemplate_NoAuthCSharp() => await MvcTemplateCore(languageOverride: null);
 
         private async Task MvcTemplateCore(string languageOverride)
@@ -103,9 +104,10 @@ namespace Templates.Test
             }
         }
 
-        [Theory]
+        [ConditionalTheory]
         [InlineData(true)]
         [InlineData(false)]
+        [SkipOnHelix("ef restore no worky")]
         public async Task MvcTemplate_IndividualAuth(bool useLocalDB)
         {
             Project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output);
@@ -220,7 +222,8 @@ namespace Templates.Test
             }
         }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("razor compilation restore no worky")]
         public async Task MvcTemplate_RazorRuntimeCompilation_BuildsAndPublishes()
         {
             Project = await ProjectFactory.GetOrCreateProject("mvc_rc", Output);
diff --git a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj
index c2774689163..8f1984b78a8 100644
--- a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj
+++ b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj
@@ -10,10 +10,9 @@
 
     <RunTemplateTests Condition="'$(RunTemplateTests)' == ''" >true</RunTemplateTests>
     <SkipTests Condition="'$(RunTemplateTests)' != 'true'">true</SkipTests>
-
-    <!-- Tests do not work on Helix or when bin/ directory is not in project directory due to undeclared dependency on test content. -->
-    <!-- https://github.com/dotnet/aspnetcore/issues/6857 -->
-    <BuildHelixPayload>false</BuildHelixPayload>
+    <SkipHelixArm>true</SkipHelixArm>
+    
+    <!-- Some tests still do not work on Helix, so continue running these on azdo for now-->
     <SkipTests Condition="'$(RunTemplateTests)' == 'true'">false</SkipTests>
     <BaseOutputPath />
     <OutputPath />
@@ -24,12 +23,14 @@
     <TestPackageRestorePath>$([MSBuild]::EnsureTrailingSlash('$(RepoRoot)'))obj\template-restore\</TestPackageRestorePath>
     <TestTemplateTestsProps>TemplateTests.props</TestTemplateTestsProps>
     <GenerateLoggingTestingAssemblyAttributes>false</GenerateLoggingTestingAssemblyAttributes>
+    <TestDependsOnAspNetRuntime>true</TestDependsOnAspNetRuntime>
   </PropertyGroup>
 
   <ItemGroup>
     <EmbeddedResource Include="template-baselines.json" />
     <Compile Include="$(SharedSourceRoot)Process\*.cs" LinkBase="shared\Process" />
     <Compile Include="$(SharedSourceRoot)CertificateGeneration\**\*.cs" LinkBase="shared\CertificateGeneration" />
+    <Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs
index 0d02a56f8f8..f8d6bd65b59 100644
--- a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs
+++ b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
 using Templates.Test.Helpers;
 using Xunit;
 using Xunit.Abstractions;
+using Microsoft.AspNetCore.Testing;
 
 namespace Templates.Test
 {
@@ -40,7 +41,8 @@ namespace Templates.Test
             Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult));
         }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("restore no worky")]
         public async Task RazorClassLibraryTemplateAsync()
         {
             Project = await ProjectFactory.GetOrCreateProject("razorclasslib", Output);
diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs
index 818a5745364..73e4a5da527 100644
--- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs
+++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs
@@ -25,7 +25,8 @@ namespace Templates.Test
 
         public ITestOutputHelper Output { get; }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
         public async Task RazorPagesTemplate_NoAuth()
         {
             Project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output);
@@ -93,9 +94,10 @@ namespace Templates.Test
             }
         }
 
-        [Theory]
+        [ConditionalTheory]
         [InlineData(false)]
         [InlineData(true)]
+        [SkipOnHelix("ef restore no worky")]
         public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB)
         {
             Project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output);
@@ -210,7 +212,8 @@ namespace Templates.Test
             }
         }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("runtime compliation restore no worky")]
         public async Task RazorPagesTemplate_RazorRuntimeCompilation_BuildsAndPublishes()
         {
             Project = await ProjectFactory.GetOrCreateProject("razorpages_rc", Output);
diff --git a/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs
index e1d5db13388..fc787751892 100644
--- a/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs
+++ b/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs
@@ -15,15 +15,18 @@ namespace Templates.Test.SpaTemplateTest
         public AngularTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
             : base(projectFactory, browserFixture, output) { }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task AngularTemplate_Works()
             => SpaTemplateImplAsync("angularnoauth", "angular", useLocalDb: false, usesAuth: false);
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task AngularTemplate_IndividualAuth_Works()
             => SpaTemplateImplAsync("angularindividual", "angular", useLocalDb: false, usesAuth: true);
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task AngularTemplate_IndividualAuth_Works_LocalDb()
             => SpaTemplateImplAsync("angularindividualuld", "angular", useLocalDb: true, usesAuth: true);
     }
diff --git a/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs
index 44d6b67f325..3e32514cc1e 100644
--- a/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs
+++ b/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs
@@ -3,6 +3,7 @@
 
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.E2ETesting;
+using Microsoft.AspNetCore.Testing;
 using Templates.Test.Helpers;
 using Xunit;
 using Xunit.Abstractions;
@@ -16,7 +17,8 @@ namespace Templates.Test.SpaTemplateTest
         {
         }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task ReactReduxTemplate_Works_NetCore()
             => SpaTemplateImplAsync("reactredux", "reactredux", useLocalDb: false, usesAuth: false);
     }
diff --git a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs
index 469e87acd56..a2e1c1c3685 100644
--- a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs
+++ b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs
@@ -17,15 +17,18 @@ namespace Templates.Test.SpaTemplateTest
         {
         }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task ReactTemplate_Works_NetCore()
             => SpaTemplateImplAsync("reactnoauth", "react", useLocalDb: false, usesAuth: false);
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task ReactTemplate_IndividualAuth_NetCore()
             => SpaTemplateImplAsync("reactindividual", "react", useLocalDb: false, usesAuth: true);
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("selenium")]
         public Task ReactTemplate_IndividualAuth_NetCore_LocalDb()
             => SpaTemplateImplAsync("reactindividualuld", "react", useLocalDb: true, usesAuth: true);
     }
diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs
index 375f2924726..2f9a5ce1624 100644
--- a/src/ProjectTemplates/test/WebApiTemplateTest.cs
+++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs
@@ -3,6 +3,7 @@
 
 using System.Threading.Tasks;
 using Templates.Test.Helpers;
+using Microsoft.AspNetCore.Testing;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -25,7 +26,8 @@ namespace Templates.Test
         [Fact]
         public async Task WebApiTemplateFSharp() => await WebApiTemplateCore(languageOverride: "F#");
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")]
         public async Task WebApiTemplateCSharp() => await WebApiTemplateCore(languageOverride: null);
 
         private async Task WebApiTemplateCore(string languageOverride)
diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs
index 738eafc61d6..0299f17a118 100644
--- a/src/ProjectTemplates/test/WorkerTemplateTest.cs
+++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
 using Templates.Test.Helpers;
 using Xunit;
 using Xunit.Abstractions;
+using Microsoft.AspNetCore.Testing;
 
 namespace Templates.Test
 {
@@ -20,7 +21,8 @@ namespace Templates.Test
         public ProjectFactoryFixture ProjectFactory { get; }
         public ITestOutputHelper Output { get; }
 
-        [Fact]
+        [ConditionalFact]
+        [SkipOnHelix("restore no worky")]        
         public async Task WorkerTemplateAsync()
         {
             Project = await ProjectFactory.GetOrCreateProject("worker", Output);
diff --git a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs
index 8ca56542437..fd0878aec2f 100644
--- a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs
+++ b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs
@@ -113,6 +113,13 @@ namespace Microsoft.AspNetCore.E2ETesting
             // It's important that we get the folder value before we start the process to prevent
             // untracked processes when the tracking folder is not correctly configure.
             var trackingFolder = GetProcessTrackingFolder();
+            if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix")))
+            {
+                // Just create a random tracking folder on helix
+                trackingFolder = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
+                Directory.CreateDirectory(trackingFolder);
+            }
+            
             if (!Directory.Exists(trackingFolder))
             {
                 throw new InvalidOperationException($"Invalid tracking folder. Set the 'SeleniumProcessTrackingFolder' MSBuild property to a valid folder.");
-- 
GitLab