Skip to content

Commit e1f7914

Browse files
authored
Merge pull request #19 from FlaUI/app-working-dir-capability
Add appium:appWorkingDir capability and return only matched
2 parents fef5489 + 2fb5762 commit e1f7914

File tree

4 files changed

+103
-11
lines changed

4 files changed

+103
-11
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The following capabilities are supported:
2626
| platformName | Must be set to `windows` (case-insensitive). | `windows` |
2727
| appium:app | The path to the application, or in case of an UWP app, `<package family name>!App`. It is also possible to set app to `Root`. In such case the session will be invoked without any explicit target application. Either this capability, `appTopLevelWindow` or `appTopLevelWindowTitleMatch` must be provided on session startup. | `C:\Windows\System32\notepad.exe`, `Microsoft.WindowsCalculator_8wekyb3d8bbwe!App` |
2828
| appium:appArguments | Application arguments string, for example `/?`. | |
29+
| appium:appWorkingDir | Full path to the folder, which is going to be set as the working dir for the application under test. This is only applicable for classic apps. When this is used the `appium:app` may contain a relative file path. | `C:\MyApp\` |
2930
| appium:appTopLevelWindow | The hexadecimal handle of an existing application top level window to attach to, for example `0x12345` (should be of string type). Either this capability, `appTopLevelWindowTitleMatch` or `app` must be provided on session startup. | `0xC0B46` |
3031
| appium:appTopLevelWindowTitleMatch | The title of an existing application top level window to attach to, for example `My App Window Title` (should be of string type). Either this capability, `appTopLevelWindow` or `app` must be provided on session startup. | `My App Window Title` or `My App Window Title - .*` |
3132
| appium:newCommandTimeout | The number of seconds the to wait for clients to send commands before deciding that the client has gone away and the session should shut down. Default one minute (60). | `120` |

src/FlaUI.WebDriver.UITests/SessionTests.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,23 @@ namespace FlaUI.WebDriver.UITests
1010
public class SessionTests
1111
{
1212
[Test]
13-
public void NewSession_CapabilitiesDoNotMatch_ReturnsError()
13+
public void NewSession_PlatformNameMissing_ReturnsError()
1414
{
1515
var emptyOptions = FlaUIDriverOptions.Empty();
1616

1717
var newSession = () => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, emptyOptions);
1818

19-
Assert.That(newSession, Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required (SessionNotCreated)"));
19+
Assert.That(newSession, Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)"));
20+
}
21+
22+
[Test]
23+
public void NewSession_AllAppCapabilitiesMissing_ReturnsError()
24+
{
25+
var emptyOptions = FlaUIDriverOptions.Empty();
26+
emptyOptions.AddAdditionalOption("appium:platformName", "windows");
27+
var newSession = () => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, emptyOptions);
28+
29+
Assert.That(newSession, Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)"));
2030
}
2131

2232
[Test]
@@ -54,6 +64,28 @@ public void NewSession_AppNotAString_Throws(object value)
5464
Throws.TypeOf<WebDriverArgumentException>().With.Message.EqualTo("Capability appium:app must be a string"));
5565
}
5666

67+
[Test]
68+
public void NewSession_AppWorkingDir_IsSupported()
69+
{
70+
var driverOptions = FlaUIDriverOptions.TestApp();
71+
driverOptions.AddAdditionalOption("appium:appWorkingDir", "C:\\");
72+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
73+
74+
var title = driver.Title;
75+
76+
Assert.That(title, Is.EqualTo("FlaUI WPF Test App"));
77+
}
78+
79+
[Test]
80+
public void NewSession_NotSupportedCapability_Throws()
81+
{
82+
var driverOptions = FlaUIDriverOptions.TestApp();
83+
driverOptions.AddAdditionalOption("unknown:unknown", "value");
84+
85+
Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions),
86+
Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability (SessionNotCreated)"));
87+
}
88+
5789
[Test]
5890
public void NewSession_AppTopLevelWindow_IsSupported()
5991
{

src/FlaUI.WebDriver/Controllers/SessionController.cs

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Diagnostics.CodeAnalysis;
88
using System.Linq;
99
using System.Text.Json;
10+
using System.Text.Json.Nodes;
1011
using System.Text.RegularExpressions;
1112
using System.Threading.Tasks;
1213

@@ -29,9 +30,10 @@ public SessionController(ILogger<SessionController> logger, ISessionRepository s
2930
public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest request)
3031
{
3132
var possibleCapabilities = GetPossibleCapabilities(request);
32-
var matchingCapabilities = possibleCapabilities.Where(
33-
capabilities => capabilities.TryGetValue("platformName", out var platformName) && platformName.GetString()?.ToLowerInvariant() == "windows"
34-
);
33+
IDictionary<string, JsonElement>? matchedCapabilities = null;
34+
IEnumerable<IDictionary<string, JsonElement>> matchingCapabilities = possibleCapabilities
35+
.Where(capabilities => IsMatchingCapabilitySet(capabilities, out matchedCapabilities))
36+
.Select(capabillities => matchedCapabilities!);
3537

3638
Core.Application? app;
3739
var capabilities = matchingCapabilities.FirstOrDefault();
@@ -40,7 +42,7 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
4042
return WebDriverResult.Error(new ErrorResponse
4143
{
4244
ErrorCode = "session not created",
43-
Message = "Required capabilities did not match. Capability `platformName` with value `windows` is required"
45+
Message = "Required capabilities did not match. Capability `platformName` with value `windows` is required, and one of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability"
4446
});
4547
}
4648
if (TryGetStringCapability(capabilities, "appium:app", out var appPath))
@@ -61,6 +63,10 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
6163
else
6264
{
6365
var processStartInfo = new ProcessStartInfo(appPath, appArguments ?? "");
66+
if(TryGetStringCapability(capabilities, "appium:appWorkingDir", out var appWorkingDir))
67+
{
68+
processStartInfo.WorkingDirectory = appWorkingDir;
69+
}
6470
app = Core.Application.Launch(processStartInfo);
6571
}
6672
}
@@ -98,7 +104,60 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
98104
}));
99105
}
100106

101-
private static bool TryGetStringCapability(Dictionary<string, JsonElement> capabilities, string key, [MaybeNullWhen(false)] out string value)
107+
private bool IsMatchingCapabilitySet(IDictionary<string, JsonElement> capabilities, out IDictionary<string, JsonElement> matchedCapabilities)
108+
{
109+
matchedCapabilities = new Dictionary<string, JsonElement>();
110+
if (TryGetStringCapability(capabilities, "platformName", out var platformName)
111+
&& platformName.ToLowerInvariant() == "windows")
112+
{
113+
matchedCapabilities.Add("platformName", capabilities["platformName"]);
114+
}
115+
else
116+
{
117+
return false;
118+
}
119+
120+
if (TryGetStringCapability(capabilities, "appium:app", out var appPath))
121+
{
122+
matchedCapabilities.Add("appium:app", capabilities["appium:app"]);
123+
124+
if (appPath != "Root")
125+
{
126+
if(TryGetStringCapability(capabilities, "appium:appArguments", out _))
127+
{
128+
matchedCapabilities.Add("appium:appArguments", capabilities["appium:appArguments"]);
129+
}
130+
if (!appPath.EndsWith("!App"))
131+
{
132+
if (TryGetStringCapability(capabilities, "appium:appWorkingDir", out _))
133+
{
134+
matchedCapabilities.Add("appium:appWorkingDir", capabilities["appium:appWorkingDir"]);
135+
}
136+
}
137+
}
138+
}
139+
else if (TryGetStringCapability(capabilities, "appium:appTopLevelWindow", out _))
140+
{
141+
matchedCapabilities.Add("appium:appTopLevelWindow", capabilities["appium:appTopLevelWindow"]);
142+
}
143+
else if (TryGetStringCapability(capabilities, "appium:appTopLevelWindowTitleMatch", out _))
144+
{
145+
matchedCapabilities.Add("appium:appTopLevelWindowTitleMatch", capabilities["appium:appTopLevelWindowTitleMatch"]);
146+
}
147+
else
148+
{
149+
return false;
150+
}
151+
152+
if (TryGetNumberCapability(capabilities, "appium:newCommandTimeout", out _))
153+
{
154+
matchedCapabilities.Add("appium:newCommandTimeout", capabilities["appium:newCommandTimeout"]); ;
155+
}
156+
157+
return matchedCapabilities.Count == capabilities.Count;
158+
}
159+
160+
private static bool TryGetStringCapability(IDictionary<string, JsonElement> capabilities, string key, [MaybeNullWhen(false)] out string value)
102161
{
103162
if(capabilities.TryGetValue(key, out var valueJson))
104163
{
@@ -115,7 +174,7 @@ private static bool TryGetStringCapability(Dictionary<string, JsonElement> capab
115174
return false;
116175
}
117176

118-
private static bool TryGetNumberCapability(Dictionary<string, JsonElement> capabilities, string key, out double value)
177+
private static bool TryGetNumberCapability(IDictionary<string, JsonElement> capabilities, string key, out double value)
119178
{
120179
if (capabilities.TryGetValue(key, out var valueJson))
121180
{
@@ -178,14 +237,14 @@ private static Process GetProcessByMainWindowHandle(string appTopLevelWindowStri
178237
return process;
179238
}
180239

181-
private static IEnumerable<Dictionary<string, JsonElement>> GetPossibleCapabilities(CreateSessionRequest request)
240+
private static IEnumerable<IDictionary<string, JsonElement>> GetPossibleCapabilities(CreateSessionRequest request)
182241
{
183242
var requiredCapabilities = request.Capabilities.AlwaysMatch ?? new Dictionary<string, JsonElement>();
184243
var allFirstMatchCapabilities = request.Capabilities.FirstMatch ?? new List<Dictionary<string, JsonElement>>(new[] { new Dictionary<string, JsonElement>() });
185244
return allFirstMatchCapabilities.Select(firstMatchCapabilities => MergeCapabilities(firstMatchCapabilities, requiredCapabilities));
186245
}
187246

188-
private static Dictionary<string, JsonElement> MergeCapabilities(Dictionary<string, JsonElement> firstMatchCapabilities, Dictionary<string, JsonElement> requiredCapabilities)
247+
private static IDictionary<string, JsonElement> MergeCapabilities(IDictionary<string, JsonElement> firstMatchCapabilities, IDictionary<string, JsonElement> requiredCapabilities)
189248
{
190249
var duplicateKeys = firstMatchCapabilities.Keys.Intersect(requiredCapabilities.Keys);
191250
if (duplicateKeys.Any())

src/FlaUI.WebDriver/Models/CreateSessionResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace FlaUI.WebDriver.Models
66
public class CreateSessionResponse
77
{
88
public string SessionId { get; set; } = null!;
9-
public Dictionary<string, JsonElement> Capabilities { get; set; } = new Dictionary<string, JsonElement>();
9+
public IDictionary<string, JsonElement> Capabilities { get; set; } = new Dictionary<string, JsonElement>();
1010
}
1111
}

0 commit comments

Comments
 (0)