-
Notifications
You must be signed in to change notification settings - Fork 6
/
FrontendUITestBase.cs
100 lines (94 loc) · 6.95 KB
/
FrontendUITestBase.cs
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
using Lombiq.Tests.UI.Extensions;
using Lombiq.Tests.UI.Services;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace Lombiq.Tests.UI.Samples;
// Sometimes Orchard Core is used in a headless manner, as a web API server that the visitors reach through a web
// frontend (for example a Vue or React single page application). In this scenario you can test the API directly and
// test the frontend with a dummy backend separately, but that will only get you so far. You'll want some tests that
// ensure the two work together. To make this happen, you need some custom logic to initialize the frontend process
// after setup, with a unique port number to avoid clashes. Also the frontend may need the backend URL, whose port is
// already randomized with every test.
// In this base class we define a custom "setup and test" method. Creating such a method is a good practice so you don't
// have to configure the FrontendServer over and over again in every test. We placed this method into its own
// intermediate abstract class for better organization, but you can also put it into your UITestBase as well.
public abstract class FrontendUITestBase : UITestBase
{
protected FrontendUITestBase(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
/// <summary>
/// Executes a UI test where the frontend is served by a separate process.
/// </summary>
[SuppressMessage("Style", "IDE0055:Fix formatting", Justification = "Needed for more readable comments.")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1114:Parameter list should follow declaration", Justification = "Same.")]
protected Task ExecuteFrontendTestAfterSetupAsync(
Func<UITestContext, Task> testAsync,
Browser browser,
Func<OrchardCoreUITestExecutorConfiguration, Task> changeConfigurationAsync = null) =>
ExecuteTestAfterSetupAsync(
async context =>
{
// Before executing provided test, we switch to the frontend URL, as if we switched to a different
// tenant, then actually navigate to the frontend so the tests won't start in the backend home page
// that's probably not used anyway.
context.SwitchToFrontend();
await context.GoToHomePageAsync();
await testAsync(context);
},
browser,
configuration =>
{
// The FrontendServer instance manages the test configuration by registering event handlers for the
// application startup and stop. It also initializes the default frontend and backend URLs. Below the
// "name" is the label you will find in the test application logs in front of all lines that come from
// this process. The process's arguments can be set with the arguments array, but likely you'll want to
// set pass in the frontend and backend ports too which you can do in the "configureCommand" function.
new FrontendServer(name: "http-server (test frontend)", configuration, _testOutputHelper)
.Configure(
// Here we use NPX which executes an NPM package without locally installing it, to avoid having
// to maintain a separate binary or script just for this sample.
program: "npx",
arguments: null,
// This function lets you edit the command dynamically before the process is created. This is
// needed to add the frontend and backend URLs or port numbers to the process, e.g. as arguments
// or environment variables. Since we set the arguments here, the parameter above is left null.
// If necessary, this is also where you'd set the program's working directory.
configureCommand: (frontendCommand, context) =>
{
// You can also get this from the configuration using configuration
// .GetFrontendAndBackendUris().FrontendUri.Port. The values in the configuration have been
// initialized shortly before this function is called, but it's not worth it unless you need
// both frontend and backend URLS. The frontend port is a unique, reserved number that's
// guaranteed to be available during this test just as much as any Orchard Core instance,
// because it's coming from the same pool of numbers.
var port = context.FrontendPort.ToTechnicalString();
// Here we configure NPX to automatically download http-server without prompting (--yes) and
// use the provided port number. Since this server uses HTTP instead of HTTPS, you have to
// set the frontend URL too. The backend URL is not changed, so pass null to leave it as-is.
configuration.SetFrontendAndBackendUris(
frontendUrl: $"http://localhost:" + port,
backendUrl: null);
return frontendCommand.WithArguments(["--yes", "http-server", "--port", port]);
},
// When this function is not null, test setup will call it on each output line and wait for it
// to return true. This can be used to look for an output that only appears when the frontend
// server is done with its own startup.
checkProgramReady: (line, _) => line?.Contains("Hit CTRL-C to stop the server") == true,
// You can use this async function to perform additional tasks right before the FrontendServer's
// BeforeAppStart event handler is finished. You can also edit the Orchard Core instance's
// startup command here. We don't need it right now.
thenAsync: _ => Task.CompletedTask,
// Don't start up the server during setup and snapshot restore. You'd rarely want that, so we
// have prepared a static method to generate a callback for this parameter.
skipStartup: FrontendServer.SkipDuringSetupAndRestore(configuration),
// Since we call NPX, the process may start with downloading from the network. So we allow some
// grace period here, but it's unlikely that it would take too long unless the program is stuck
// or frozen somehow. In that case it's better to fail than wait forever.
startupTimeout: TimeSpan.FromMinutes(3));
return changeConfigurationAsync.InvokeFuncAsync(configuration);
});
}