Skip to content

Commit 0fbffec

Browse files
authored
Implement support for Mac platform (#309)
1 parent 2c6f0d6 commit 0fbffec

File tree

12 files changed

+385
-72
lines changed

12 files changed

+385
-72
lines changed

docs/JavaScriptEnhancements.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class JavaScriptExample {
5050
// Execute script as anonymous function, passing specified argument
5151
WebElement response = JsUtility.runAndReturn(driver, script, "viewport");
5252
// If element reference was returned, extract 'content' attribute
53-
return (response == null) ? null : response.getAttribute("content");
53+
return (response == null) ? null : response.getDomAttribute("content");
5454
}
5555
}
5656
```
@@ -61,7 +61,7 @@ The following code is sample JavaScript file <getMetaTagByName.js>. This f
6161
```javascript
6262
var found = document.getElementsByTagName("meta");
6363
for (var i = 0; i < found.length; i++) {
64-
if (found[i].getAttribute("name") == arguments[0]) return found[i];
64+
if (found[i].getDomAttribute("name") == arguments[0]) return found[i];
6565
}
6666
return null;
6767
```
@@ -109,7 +109,7 @@ public class AnotherJavaScriptExample {
109109
// Execute script as anonymous function, passing specified argument
110110
WebElement response = JsUtility.runAndReturn(driver, script, name);
111111
// Extract 'content' attribute
112-
return response.getAttribute("content");
112+
return response.getDomAttribute("content");
113113
} catch (WebDriverException e) {
114114
// Extract encoded exception
115115
throw JsUtility.propagate(e);
@@ -124,7 +124,7 @@ The following code is the sample JavaScript file &lt;requireMetaTagByName.js&gt;
124124
```javascript
125125
var found = document.getElementsByTagName("meta");
126126
for (var i = 0; i < found.length; i++) {
127-
if (found[i].getAttribute("name") == arguments[0]) return found[i];
127+
if (found[i].getDomAttribute("name") == arguments[0]) return found[i];
128128
}
129129
throwNew('org.openqa.selenium.NoSuchElementException', 'No meta element found with name: ' + arguments[0]);
130130
```

docs/example/TableComponent.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public class TableComponent extends PageComponent {
6969
}
7070

7171
public static Object getKey(SearchContext context) {
72-
return ((WebElement) context).getAttribute("id");
72+
return ((WebElement) context).getDomAttribute("id");
7373
}
7474
}
7575
```

src/main/java/com/nordstrom/automation/selenium/examples/MacPage.java

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Set;
6+
import java.util.stream.Collectors;
7+
8+
import org.openqa.selenium.By;
9+
import org.openqa.selenium.SearchContext;
10+
import org.openqa.selenium.TimeoutException;
11+
import org.openqa.selenium.WebDriver;
12+
import com.nordstrom.automation.selenium.core.JsUtility;
13+
import com.nordstrom.automation.selenium.exceptions.NoDocumentAppearedTimeoutException;
14+
import com.nordstrom.automation.selenium.model.Page;
15+
import com.nordstrom.automation.selenium.model.PageComponent;
16+
import com.nordstrom.automation.selenium.support.Coordinator;
17+
18+
/**
19+
* This class is the model for the <b>TextEdit</b> application.
20+
*/
21+
public class TextEditApplication extends Page {
22+
23+
/**
24+
* Constructor for main document context
25+
*
26+
* @param driver driver object
27+
*/
28+
public TextEditApplication(WebDriver driver) {
29+
super(driver);
30+
}
31+
32+
private static final String DOC_WINDOW_TEMPLATE = ".//XCUIElementTypeWindow[@title='%s']";
33+
34+
private static final Map<String, Object> CMD_N =
35+
Map.of("keys", List.of(Map.of("key", "n", "modifierFlags", 1 << 4)));
36+
private static final Map<String, Object> CMD_O =
37+
Map.of("keys", List.of(Map.of("key", "o", "modifierFlags", 1 << 4)));
38+
39+
private TextEditManagementPanel managementPanel;
40+
41+
/**
42+
* This enumeration defines element locator constants.
43+
*/
44+
protected enum Using implements ByEnum {
45+
/** <b>TextEdit</b> document management panel element */
46+
MANAGEMENT_PANEL(By.xpath(".//XCUIElementTypeWindow[@title='Open']")),
47+
/** <b>TextEdit</b> document window element */
48+
DOCUMENT_WINDOW(By.xpath(".//XCUIElementTypeWindow[@identifier='_NS:34']"));
49+
50+
private final By locator;
51+
52+
Using(By locator) {
53+
this.locator = locator;
54+
}
55+
56+
@Override
57+
public By locator() {
58+
return locator;
59+
}
60+
}
61+
62+
/**
63+
* Open the <b>TextEdit</b> document management panel.
64+
*
65+
* @return {@link TextEditManagementPanel} component
66+
*/
67+
public TextEditManagementPanel openManagementPanel() {
68+
if (managementPanel == null || managementPanel.isInvisible()) {
69+
JsUtility.run(driver, "macos: keys", CMD_O);
70+
managementPanel = new TextEditManagementPanel(Using.MANAGEMENT_PANEL.locator, this);
71+
}
72+
return managementPanel;
73+
}
74+
75+
/**
76+
* Open a new <b>TextEdit</b> document.
77+
*
78+
* @return {@link TextEditDocumentWindow} component
79+
*/
80+
public TextEditDocumentWindow openNewDocument() {
81+
Set<String> initialNames = getOpenDocumentNames();
82+
JsUtility.run(driver, "macos: keys", CMD_N);
83+
return (TextEditDocumentWindow) getWait().until(newDocumentIsOpened(initialNames));
84+
}
85+
86+
/**
87+
* Get the titles of all open <b>TextEdit</b> document windows.
88+
*
89+
* @return set of document window names
90+
*/
91+
public Set<String> getOpenDocumentNames() {
92+
return driver.findElements(Using.DOCUMENT_WINDOW.locator).stream()
93+
.map(e -> e.getDomAttribute("title")).collect(Collectors.toSet());
94+
}
95+
96+
/**
97+
* Returns a 'wait' proxy that determines if a new document has opened.
98+
*
99+
* @param initialNames initial set of document window names
100+
* @return document window component if visible; otherwise 'null'
101+
*/
102+
public static Coordinator<PageComponent> newDocumentIsOpened(final Set<String> initialNames) {
103+
return new Coordinator<PageComponent>() {
104+
105+
/**
106+
* {@inheritDoc}
107+
*/
108+
@Override
109+
public PageComponent apply(final SearchContext context) {
110+
if (!(context instanceof TextEditApplication)) {
111+
throw new IllegalArgumentException("[context] must be 'TextEditApplication'; got '"
112+
+ context.getClass().getSimpleName() + "'");
113+
}
114+
TextEditApplication application = TextEditApplication.class.cast(context);
115+
Set<String> currentNames = application.getOpenDocumentNames();
116+
currentNames.removeAll(initialNames);
117+
if (currentNames.isEmpty()) {
118+
return null;
119+
} else {
120+
String name = currentNames.iterator().next();
121+
String xpath = DOC_WINDOW_TEMPLATE.formatted(name);
122+
By locator = By.xpath(xpath);
123+
return new TextEditDocumentWindow(locator, application);
124+
}
125+
}
126+
127+
/**
128+
* {@inheritDoc}
129+
*/
130+
@Override
131+
public String toString() {
132+
return "new document to be opened";
133+
}
134+
135+
/**
136+
* {@inheritDoc}
137+
*/
138+
@Override
139+
public TimeoutException differentiateTimeout(TimeoutException e) {
140+
return new NoDocumentAppearedTimeoutException(e.getMessage(), e.getCause());
141+
}
142+
};
143+
}
144+
145+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import org.openqa.selenium.By;
4+
import com.nordstrom.automation.selenium.model.ComponentContainer;
5+
import com.nordstrom.automation.selenium.model.PageComponent;
6+
7+
/**
8+
* This class is the model for the <b>TextEdit</b> document 'save' sheet.
9+
*/
10+
public class TextEditDocumentSaveSheet extends PageComponent {
11+
12+
/**
13+
* Constructor for page component by element locator
14+
*
15+
* @param locator component context element locator
16+
* @param parent component parent container
17+
*/
18+
public TextEditDocumentSaveSheet(By locator, ComponentContainer parent) {
19+
super(locator, parent);
20+
}
21+
22+
/**
23+
* This enumeration defines element locator constants.
24+
*/
25+
protected enum Using implements ByEnum {
26+
/** 'save' sheet <b>Delete</b> button element */
27+
DELETE_BUTTON(By.xpath(".//XCUIElementTypeButton[@title='Delete']"));
28+
29+
private final By locator;
30+
31+
Using(By locator) {
32+
this.locator = locator;
33+
}
34+
35+
@Override
36+
public By locator() {
37+
return locator;
38+
}
39+
}
40+
41+
/**
42+
* Delete the document associated with this 'save' sheet.
43+
*/
44+
public void deleteDocument() {
45+
findElement(Using.DELETE_BUTTON).click();
46+
getWait().until(componentIsHidden());
47+
}
48+
49+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import org.openqa.selenium.By;
4+
import com.nordstrom.automation.selenium.model.ComponentContainer;
5+
import com.nordstrom.automation.selenium.model.PageComponent;
6+
7+
/**
8+
* This class is the model for a <b>TextEdit</b> document window.
9+
*/
10+
public class TextEditDocumentWindow extends PageComponent {
11+
12+
/**
13+
* Constructor for page component by element locator
14+
*
15+
* @param locator component context element locator
16+
* @param parent component parent container
17+
*/
18+
public TextEditDocumentWindow(By locator, ComponentContainer parent) {
19+
super(locator, parent);
20+
}
21+
22+
/**
23+
* This enumeration defines element locator constants.
24+
*/
25+
protected enum Using implements ByEnum {
26+
/** document text view element */
27+
TEXT_VIEW(By.className("XCUIElementTypeTextView")),
28+
/** document <b>Close</b> button element */
29+
CLOSE_BUTTON(By.xpath(".//XCUIElementTypeButton[@identifier='_XCUI:CloseWindow']")),
30+
/** document <b>Save</b> sheet element */
31+
SAVE_SHEET(By.xpath(".//XCUIElementTypeSheet[@label='save']"));
32+
33+
private final By locator;
34+
35+
Using(By locator) {
36+
this.locator = locator;
37+
}
38+
39+
@Override
40+
public By locator() {
41+
return locator;
42+
}
43+
}
44+
45+
/**
46+
* Get the title of this <b>TextEdit</b> document window.
47+
*
48+
* @return document window title
49+
*/
50+
public String getDocumentTitle() {
51+
return getWrappedElement().getDomAttribute("title");
52+
}
53+
54+
/**
55+
* Populate text view of this <b>TextEdit</b> document with the specified string.
56+
*
57+
* @param keys string to type into document text view
58+
*/
59+
public void modifyDocument(String keys) {
60+
findElement(Using.TEXT_VIEW).sendKeys(keys);
61+
}
62+
63+
/**
64+
* Get content of the text view of this <b>TextEdit</b> document.
65+
*
66+
* @return document text view content
67+
*/
68+
public String getDocumentContent() {
69+
return findElement(Using.TEXT_VIEW).getText();
70+
}
71+
72+
/**
73+
* Close this <b>TextEdit</b> document without saving.
74+
*/
75+
public void closeDocumentWithoutSaving() {
76+
findElement(Using.CLOSE_BUTTON).click();
77+
new TextEditDocumentSaveSheet(Using.SAVE_SHEET.locator, this).deleteDocument();
78+
getWait().until(componentIsHidden());
79+
}
80+
81+
}

0 commit comments

Comments
 (0)