Skip to content

Commit dadd985

Browse files
authored
Merge pull request #369 from csf-dev/craigfowler/issue225
Resolve #225 (sample task docs)
2 parents 81eba96 + fce0e08 commit dadd985

7 files changed

Lines changed: 114 additions & 21 deletions

File tree

CSF.Screenplay.Docs/docs/extensions/selenium/Actions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,16 @@ Below is a summary of each of these actions' usage, with a link to their detaile
4545

4646
| Action | Usage |
4747
| ------ | ----- |
48+
| [`BeginCollectingLogsWithJavaScript`] | Runs a JavaScript workaround to collect browser logs, see [`ShouldCollectLogs`] for more info |
4849
| [`ClearCookies`] | Clear the web browser cookies for the current site (domain) |
4950
| [`ClearLocalStorage`] | Clear the web browser [local storage] for the current site (domain) |
5051
| [`DeleteTheCookie`] | Delete a single named cookie |
5152
| [`ExecuteJavaScript`] | Executes a JavaScript directly in the browser |
5253
| [`OpenUrl`] | Directs the browser to open a specified URL |
5354
| [`SaveScreenshot`] | Saves a screenshot to a file |
5455

56+
[`BeginCollectingLogsWithJavaScript`]: xref:CSF.Screenplay.Selenium.Actions.BeginCollectingLogsWithJavaScript
57+
[`ShouldCollectLogs`]: xref:CSF.Screenplay.Selenium.BrowseTheWeb.ShouldCollectLogs
5558
[`OpenUrl`]: xref:CSF.Screenplay.Selenium.Actions.OpenUrl
5659
[`SaveScreenshot`]: xref:CSF.Screenplay.Selenium.Actions.SaveScreenshot
5760
[`ClearCookies`]: xref:CSF.Screenplay.Selenium.Actions.ClearCookies

CSF.Screenplay.Docs/docs/extensions/selenium/Questions.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,23 @@ The possibilities are explained in detail on the questions' detailed documentati
3838

3939
| Question | Usage |
4040
| -------- | ----- |
41+
| [`FilterElements`] | Filters [a collection of elements] for those which match a [query] |
4142
| [`FindElement`] | Gets [an element] which matches a [`Locator`] |
4243
| [`FindElements`] | Gets [a collection of elements] which match a [`Locator`] |
43-
| [`FilterElements`] | Filters [a collection of elements] for those which match a [query] |
44+
| [`GetShadowRootNatively`] | Uses a native technique to get a [Shadow DOM] [root element] |
45+
| [`GetShadowRootWithJavaScript`] | Uses a JavaScript-based workaround to get a [Shadow DOM] [root element] |
4446

47+
[`FilterElements`]: xref:CSF.Screenplay.Selenium.Questions.FilterElements
4548
[`FindElement`]: xref:CSF.Screenplay.Selenium.Questions.FindElement
4649
[`FindElements`]: xref:CSF.Screenplay.Selenium.Questions.FindElements
47-
[`FilterElements`]: xref:CSF.Screenplay.Selenium.Questions.FilterElements
4850
[`Locator`]: xref:CSF.Screenplay.Selenium.Elements.Locator
4951
[query]: Queries.md
5052
[an element]: xref:CSF.Screenplay.Selenium.Elements.SeleniumElement
5153
[a collection of elements]: xref:CSF.Screenplay.Selenium.Elements.SeleniumElementCollection
54+
[`GetShadowRootNatively`]: xref:CSF.Screenplay.Selenium.Questions.GetShadowRootNatively
55+
[Shadow DOM]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
56+
[root element]: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
57+
[`GetShadowRootWithJavaScript`]: xref:CSF.Screenplay.Selenium.Questions.GetShadowRootWithJavaScript
5258

5359
## Questions for the web browser
5460

@@ -57,10 +63,14 @@ These questions don't neccesarily involve elements, instead they query the web b
5763
| Question | Usage |
5864
| -------- | ----- |
5965
| [`ExecuteJavaScriptAndGetResult<TResult>`] | Executes a JavaScript directly in the browser and reads the result |
66+
| [`GetLogsNatively`] | Reads the browser console logs _(note that web driver support is limited)_ |
67+
| [`GetLogsWithJavaScript`] | Reads the browser console logs using a JavaScript workaround, activated by [`BeginCollectingLogsWithJavaScript`] |
6068
| [`GetWindowTitle`] | Reads the text of the Window/Tab title |
61-
| [`TakeScreenshot`] | Takes a Screenshot of the browser window |
69+
| [`TakeScreenshot`] | Takes a Screenshot of the browser window |
6270

6371
[`ExecuteJavaScriptAndGetResult<TResult>`]: xref:CSF.Screenplay.Selenium.Questions.ExecuteJavaScriptAndGetResult`1
72+
[`GetLogsNatively`]: xref:CSF.Screenplay.Selenium.Questions.GetLogsNatively
73+
[`GetLogsWithJavaScript`]: xref:CSF.Screenplay.Selenium.Questions.GetLogsWithJavaScript
74+
[`BeginCollectingLogsWithJavaScript`]: xref:CSF.Screenplay.Selenium.Actions.BeginCollectingLogsWithJavaScript
6475
[`GetWindowTitle`]: xref:CSF.Screenplay.Selenium.Questions.GetWindowTitle
6576
[`TakeScreenshot`]: xref:CSF.Screenplay.Selenium.Questions.TakeScreenshot
66-

CSF.Screenplay.Docs/docs/extensions/selenium/Tasks.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@ The table below serves as a list, along with a brief summary of the purpose of e
88

99
| Task | Usage |
1010
| ---- | ----- |
11+
| [`BeginCollectingLogsWithJavaScriptIfApplicable`] | Conditionally executes [`BeginCollectingLogsWithJavaScript`] |
1112
| [`ClickAndWaitForDocumentReady`] | Clicks a link which navigates to a new page, waiting until it is ready |
1213
| [`EnterTheDate`] | Enters a value into an `<input type="date">` in a cross-browser manner |
13-
| [`OpenUrlRespectingBase`] | Navigates to a URL, using a base URL provided by the [`UseABaseUri`] ability |
14+
| [`GetShadowRoot`] | Uses the best technique available to get [the root element] of a [Shadow DOM] |
15+
| [`GetTheBrowserLogs`] | Uses the best technique available to get the browser console logs |
16+
| [`NavigateToUrl`] | Navigates to a specified URL, which may be relative to a base specified by the [`UseABaseUri`] ability |
1417
| [`SetTheElementValue`] | Sets the `value` of an element with JavaScript, emulating updating it interactively |
1518
| [`TakeAndSaveScreenshot`] | Convenience task to combine the taking & saving of a browser screenshot |
1619

20+
[`BeginCollectingLogsWithJavaScriptIfApplicable`]: xref:CSF.Screenplay.Selenium.Tasks.BeginCollectingLogsWithJavaScriptIfApplicable
21+
[`BeginCollectingLogsWithJavaScript`]: xref:CSF.Screenplay.Selenium.Actions.BeginCollectingLogsWithJavaScript
22+
[`GetShadowRoot`]: xref:CSF.Screenplay.Selenium.Tasks.GetShadowRoot
23+
[Shadow DOM]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
24+
[the root element]: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
25+
[`GetTheBrowserLogs`]: xref:CSF.Screenplay.Selenium.Tasks.GetTheBrowserLogs
1726
[`ClickAndWaitForDocumentReady`]: xref:CSF.Screenplay.Selenium.Tasks.ClickAndWaitForDocumentReady
1827
[`EnterTheDate`]: xref:CSF.Screenplay.Selenium.Tasks.EnterTheDate
19-
[`OpenUrlRespectingBase`]: xref:CSF.Screenplay.Selenium.Tasks.OpenUrlRespectingBase
28+
[`NavigateToUrl`]: xref:CSF.Screenplay.Selenium.Tasks.NavigateToUrl
2029
[`UseABaseUri`]: xref:CSF.Screenplay.Selenium.UseABaseUri
2130
[`SetTheElementValue`]: xref:CSF.Screenplay.Selenium.Tasks.SetTheElementValue
2231
[`TakeAndSaveScreenshot`]: xref:CSF.Screenplay.Selenium.Tasks.TakeAndSaveScreenshot

CSF.Screenplay.Docs/docs/writingPerformables/AvoidBranchingLogic.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
[Performances] are written _a lot like scripts_.
44
Where possible, [Performables] should avoid branching or looping logic.
55
_This is particularly true_ when Screenplay is being used [as a tool for testing].
6-
Good test logic has [a cyclomatic complexity] of precisely one.
6+
Good test logic has [a cyclomatic complexity] as close to one as possible.
77

88
Sometimes looping logic is unavoidable and desirable in a Screenplay, imagine a performable which has an [Actor] repeat a process `N` times.
9-
This is acceptable if used judiciously.
9+
Sometimes branching logic is required to deal with external systems which present differing behaviour.
10+
The [Selenium Extension] makes use of this technique to accomodate different web browsers and web driver implementations.
11+
Such logic is acceptable if used judiciously.
1012

11-
Performables should always avoid branching logic like `if` or `switch` though, and _should never_ contain such logic when being used for tests.
12-
If more than one mode of operation is required then write more than one performable.
13-
The path through a Performance should be completely deterministic, short of an unexpected error or failure.
13+
Perhaps the best guideline is for the developer to ask themselves:
14+
15+
> Could I separate this into two or more tasks and decide which I need at the point of usage?
16+
17+
If the answer is "yes", then it's often better to do just that, rather than write a multi-function Task.
1418

1519
[Performances]: xref:CSF.Screenplay.IPerformance
1620
[Performables]: ../../glossary/Performable.md
1721
[as a tool for testing]: ../bestPractice/SuitabilityAsATestingTool.md
1822
[a cyclomatic complexity]: https://en.wikipedia.org/wiki/Cyclomatic_complexity
1923
[Actor]: xref:CSF.Screenplay.Actor
24+
[Selenium Extension]: ../extensions/selenium/index.md

CSF.Screenplay.Docs/docs/writingPerformables/index.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,40 @@ On the other hand, it is very likely that developers will need to write [Tasks],
1212

1313
## A sample Task
1414

15-
Here is an annotated example of a [Task] which makes use of a fictitious Action and a fictitious Question.
15+
Here is an annotated example of a [Task] which makes use of a fictitious Action and Question.
1616

17-
TODO: Write this docco
17+
```csharp
18+
// A parameter is constructor injected, so that this task may be parameterised.
19+
// This is entirely optional, if no parameters are needed then none need be added.
20+
public class BuyMilkIfNeeded(int cupsOfCoffee) : IPerformable, ICanReport
21+
{
22+
// Always implement the ICanReport interface if possible.
23+
// Write a human-readable report of what the Task does.
24+
// There's no need to detail the work done by consumed performables; they will get their own reports.
25+
public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
26+
=> formatter.Format("{Actor} adds milk to their shopping list, if they need it to make {Cups} cup(s) of coffee", actor, cupsOfCoffee);
27+
28+
public async ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default)
29+
{
30+
// Use of a fictitious Question, remember to await it and to pass the cancellation token.
31+
// This question, which looks in the refridgerator and gets a decimal which represents the
32+
// quantity of an item inside, measured in litres, is ficitious.
33+
// It is consumed here from a fictitious builder method.
34+
var litresOfMilk = await actor.PerformAsync(LookInTheRefridgeratorFor("Milk").AndTellMeHowMuchRemainsInLitres(), cancellationToken);
35+
var isEnough = IsThisEnoughMilkToMakeCoffee(cupsOfCoffee, litresOfMilk);
36+
if(!isEnough)
37+
// Use of a fictitious Action, again remember to await it and pass the cancellation token.
38+
await actor.PerformAsync(AddToMyShoppingList("Milk"), cancellationToken);
39+
40+
}
41+
42+
static bool IsThisEnoughMilkToMakeCoffee(int cupsOfCoffee, decimal litresOfMilk)
43+
{
44+
// Implementation omitted, determines if the litresOfMilk param is enough milk to make
45+
// the indicated number of cupsOfCoffee.
46+
}
47+
}
48+
```
1849

1950
[Task]: ../../glossary/Task.md
2051

CSF.Screenplay.Selenium/Actions/BeginCollectingLogsWithJavaScript.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,22 @@ namespace CSF.Screenplay.Selenium.Actions
1111
/// <remarks>
1212
/// <para>
1313
/// When used, this action should be executed as soon as possible after the current page has completed loading.
14-
/// Ideally, directly after <see cref="Tasks.ClickAndWaitForDocumentReady"/>.
15-
/// Any log messages which have been sent to the native browser console before this action is executed will be missed
14+
/// Ideally, directly after <see cref="Tasks.ClickAndWaitForDocumentReady"/> or <see cref="OpenUrl"/>.
15+
/// Any log messages which have been sent to the native browser console <em>before this action is executed</em> will be missed
1616
/// and will not be available to the counterpart question which retrieves log messages: <see cref="Questions.GetLogsWithJavaScript"/>.
1717
/// </para>
1818
/// <para>
1919
/// Note that this action/the script needs to be re-run after each traditional web page navigation/reload.
2020
/// However, due to the nature of SPAs, it <em>does not need to be re-run</em> following an SPA-style navigation.
21-
/// On supported browsers, there is no harm in re-running this script when it is not needed, except for the impact on
22-
/// performance (wasted network roundtrips).
21+
/// Please note that each time this script is run, it will have a consequence of clearing any logs which have already
22+
/// been collected. So, it is advised to execute it only after actions/tasks which would cause a full web browser page
23+
/// reload (as noted above).
2324
/// </para>
2425
/// <para>
2526
/// This action is for use only with web browsers which have the <see cref="BrowserQuirks.CanGetLogsWithJavascriptWorkaround"/> quirk.
2627
/// </para>
2728
/// </remarks>
29+
/// <seealso cref="BrowseTheWeb.ShouldCollectLogs"/>
2830
public class BeginCollectingLogsWithJavaScript : IPerformable, ICanReport
2931
{
3032
/// <inheritdoc/>

CSF.Screenplay.Selenium/BrowseTheWeb.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ namespace CSF.Screenplay.Selenium
6060
/// Developers may use the configuration to store a library of available WebDriver configurations, and use a single
6161
/// environment variable to switch between them at execution time.
6262
/// </para>
63-
/// <code>
63+
/// <code language="csharp">
6464
/// using CSF.Extensions.WebDriver;
6565
/// using CSF.Screenplay.Selenium;
6666
///
@@ -97,9 +97,42 @@ public class BrowseTheWeb : ICanReport, IDisposable
9797
/// </summary>
9898
/// <remarks>
9999
/// <para>
100-
/// When this value is set to <see langword="true"/>, this will trigger usage of <see cref="Actions.BeginCollectingLogsWithJavaScript"/>
101-
/// at points where it is required. This is applicable only when the current <see cref="WebDriver"/> implementation has the
102-
/// quirk <see cref="BrowserQuirks.CanGetLogsWithJavascriptWorkaround"/>.
100+
/// Different web driver implementations have varying levels of support for providing access to web browser's console logs.
101+
/// As of June 2026, the only implementations which provide native access are local implementations of Chromium-based
102+
/// web drivers, such as <see cref="OpenQA.Selenium.Chrome.ChromeDriver"/> and <see cref="OpenQA.Selenium.Edge.EdgeDriver"/>.
103+
/// For other web browsers/drivers, as well as all remote web drivers, Selenium is unable to directly access their
104+
/// logs using a native API.
105+
/// </para>
106+
/// <para>
107+
/// When this property is set to <see langword="true"/>, it triggers behaviour which causes Screenplay to attempt to access
108+
/// the browser's console logs more aggressively. Screenplay activates a JavaScript-based workaround when native log-collection is not viable.
109+
/// That JavaScript-based approach is coordinated from the Task <see cref="Tasks.BeginCollectingLogsWithJavaScriptIfApplicable"/>
110+
/// and ultimately the Action <see cref="Actions.BeginCollectingLogsWithJavaScript"/>.
111+
/// This functionality is consumed by the logic of two Tasks:
112+
/// </para>
113+
/// <list type="bullet">
114+
/// <item><description><see cref="Tasks.NavigateToUrl"/></description></item>
115+
/// <item><description><see cref="Tasks.ClickAndWaitForDocumentReady"/></description></item>
116+
/// </list>
117+
/// <para>
118+
/// In both cases, these tasks will execute the logic of <see cref="Tasks.BeginCollectingLogsWithJavaScriptIfApplicable"/>.
119+
/// In determining whether the JavaScript approach is applicable, the task verifies that this setting is <see langword="true"/>,
120+
/// and whether the current web driver implementation requires the JavaScript workaround to get logs.
121+
/// That is - the <see cref="WebDriver"/> is <b>not</b> remote, and it <b>does</b> have the quirk
122+
/// <see cref="BrowserQuirks.CanGetLogsWithJavascriptWorkaround"/> and it <b>does not</b> have the quirk
123+
/// <see cref="BrowserQuirks.HasNativeLogsSupport"/>. If all these criteria are satisfied, then the action
124+
/// <see cref="Actions.BeginCollectingLogsWithJavaScript"/> is executed, to begin collecting logs in JavaScript.
125+
/// </para>
126+
/// <para>
127+
/// It's important to understand that the JavaScript workaround for getting console logs <em>is imperfect</em>.
128+
/// Logs are only collected from the point at which the 'begin collection' script is sent to the web browser.
129+
/// Any logs which were written before that script arrives will be missed and will be unavailable for reading.
130+
/// This is why the collection begins immediately after two tasks which cause page reloads.
131+
/// Even so, imagine the loading of a new web page, and immediately as the page loads, there is an important console message written.
132+
/// It's quite possible that - in the brief (perhaps milliseconds) delay between the page load completing and the "Begin
133+
/// collecting logs" script arriving, that the important message was already written to the logs. In this circumstance
134+
/// the important message will not be available to Screenplay. Unfortunately there is no better solution or workaround to
135+
/// this problem when using Selenium.
103136
/// </para>
104137
/// </remarks>
105138
public bool ShouldCollectLogs => collectLogs;

0 commit comments

Comments
 (0)