Skip to content

Add GUI Update #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ $ ./Scripts/bootstrap.sh -d

As we can observe from picture below we need to replace the Bundle Identifier with a unique one. This step should be done for **"WebDriverAgentLib"** and **"IntegrationApp"**. Select the team as you personal team (using personal Apple Developer Account). This modification can be done under **Signing & Capabilities** tab. Moreover, double check if under **Build Setting - Packaging - Product Bundle Identifier** the modification is visible. If not, there should also be modified.

![Image of Xcode](https://github.com/malus-security/ios-ui-exerciser/blob/master/webDriverAgent.png)
![Image of Xcode](/assets/images/webDriverAgent.png)

The last step is to connect the iPhone to the computer and run the command:
```
Expand Down
200 changes: 200 additions & 0 deletions README_Exerciser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# iOS UI Exerciser

The following describes the inner workings and the usage of the current version
of the UI Exerciser.

### Required Script Configurations

Before the actual testing, certain capabilities need to be set in order for the
script to correctly identify the Application Under Test (AUT) and be able to
perform actions on it.

These capabilities can be found in the **setUp** function at the beginning of
the script. The required fields are:

```
desired_capabilities={
'platformName': 'iOS',
'platformVersion': <iOS version on the real device>,
'udid': <device id of the real device>,
'deviceName': <device name of the real device>,
'bundleId': <id of the AUT>
}
```

### User Input

Currently, the script waits for desired XCUIElementType classes as a required
parameter and allows the user to set a timeout marker and a desired similarity
threshold.

The **accepted XCUIElementType** classes are:

- XCUIElementTypeButton;
- XCUIElementTypeCell;
- XCUIElementTypeLink;
- XCUIElementTypeImage;
- XCUIElementTypeStaticText;
- XCUIElementTypeOther.

The **timeout value** is a limit for how long the script should be running. The
algorithm will end as soon as all the server operations allow, after the time
threshold has been exceeded. For example: if the time limit is 60s but the
operations take 65s to complete, the script will end after 65s.
By default, the timeout value is set to 0, meaning the script will run indefinetly
if no value is specified.

The **similarity threshold** is a float value representing the percentage two
different screens have the same XML source code. This is needed because certain
small changes in the page's code occur when interacting with buttons, changes
that do not necessarily produce visual differences.
By default, the similarity threshold is set to 0.9.

#### User Input Exemples

```
python3 UI_Exerciser.py -c XCUIElementTypeCell XCUIElementTypeImage -t 120
```

This will run the script searching for Cells and Images, for a duration of 120s,
leaving the similarity threshold to the default value.

```
python3 UI_Exerciser.py -c XCUIElementTypeCell XCUIElementTypeImage XCUIElementTypeButton -t 60 -s 0.85
```

This command will run the script searching for Cells, Images and Buttons for 60s
and with a similarity threshold of 85%.

### Script Description

A certain class of interest is then selected for the current screen, out of the
XCUIElementType classes inputted by the user. If the list received from the
Appium server contains no elements from the desired class, a new one is selected,
without the need of making another re-query request to the Appium server for the
XML page source. As soon as no more elements from any class of interest are left
to visit, the algorithm ends.

If there are available elements, however, the script begins to tap them in a
sequential matter, checking for previously visited or invisible elements. After
every event the script checks for a screen change.


```
screens : {
'screen_hash' : {
'buttons' : [],
'next_buttons' : [(presssed_button, placeholder)],
'visited_interest_classes' : [],
'similarity_list' : {
'screen_hash' : 'similarity_value'
},
'similarity_check_performed' : False
}
}
```

**buttons** is a list of all the buttons from a screen, while next_buttons is a list
of only the buttons that trigger a screen change.

To test for a screen change, the script stores the MD5 hash of screen's XML source
code and after a button press it checks it against the hash of the current screen.

**next_buttons** is a list of all the buttons from a screen that have triggered
a screen change. The idea was to use this to get out of a situation where we have
no new buttons to press, but due to limitations of hashing XML source code for
a unique identifier, screens that look the same to a human, the script sees as
different. This leads to screens with identical *buttons* and *next_buttons* list
and reaches a loop.

*placeholder* is a 0 for the current screen. After a screen change is detected,
the required association between button and screen is updated in the previous
screen's dictionary entry.

**similarity_list** is a dictionary containing the association between a screen
hash value and a similarity percentage, calculated upon entering a new screen.
Basically, after every screen change, the similarity value of the current screen
is computed in relation to all the previous ones and afterwards the *similarity_check_performed*
value is set to True.

**visited_interest_classes** is a list of the completely visited classes for the current
screen.

#### Script Logic

***Before running the script I recommend turning on do not disturb mode!***
Receiving notifications can lead to mismatches when querying the list of buttons.

The script selects one *interest class* (e.g. XCUIElementTypeButton) to get
elements of using Appium's `find_elements_by_class_name` function.

Then script taps the first unpressed button in the current button list and
checks for a screen change. If a screen change happened, the relevant info is
recorded in the hashtable and the script re-queries the server for a new XML
source, in order to create a new dictionary entry. Then, the script selects a
new class of interest in order to create pseudo-randomness and the process begins
again. The similarity function is called during the re-parsing of the screen source
code. This ensures that in the event of a similarity being detected, the respective
hash keys changes are made before populating the list of buttons.

For every new screen, the script also counts the number of tapped or untapped
elements (the untapped elements being the sum of the invisible and visited ones).
If the sum of these elements is equal to the number of elements that the Appium
server returned for that particular class, then the script changes its current
class of interest and makes a request to the server for a new list of
interactable buttons.

### GUI Interface and User Graph Output

The latest version of the UI Exerciser implements a graphical user interface using [Gooey](https://github.com/chriskiehl/Gooey). It is a barebones graphical wrapper over the Python Argument Parser. This allows for visual information regarding the format in which the script accepts input. It also redirects terminal output directly into the Exerciser window. Examples of the user interface can be seen below:

![Exerciser Welcome Screen](/assets/images/exerciser_welcome.png)

![Exerciser Terminal Output](/assets/images/exerciser_terminal.png)

##### User Graph Output

The Exerciser uses the [NetworkX](https://networkx.org) module in order to generate a dot file. The dot file contains a directional graph, generated from the hashtable with the data gathered during the traversal of an application. For generating the .dot file the [pydot](https://pypi.org/project/pydot/) module needs to be imported. In order to process the file into a visual graph, [graphviz](https://www.graphviz.org) needs to be installed onto the local machine. The Exerciser will generate a .dot file and then you can generate the PNG file using a command similar to:

```
dot application_graph.dot −T png > output.png
```

An example of a user journey graph can be seen below:

![User Journey Graph Example](/assets/images/graphoutput.png)

Nodes marked with a 0 are nodes that have yet to be processed by the Exerciser. The leading edge from each node is labeled with the button that led to a
screen change.

### Known Limitations

##### Appium Server Connection Problems

There are events where the Appium Server closes its socket without warning.
This is solved closing the server window and the singleton test manager and by
disconnecting the USB cable and reconnecting the device again. In other terms,
a full reset of the testing environment solves the issue, but the current test
results will be discarded. Moreover, interrupting the script (with SIGINT) and
restarting it can sometimes produce the same socket error.

##### Exiting the AUT

Another way the script will encounter errors is if, by triggering a certain
button, the AUT leaves the foreground. This can happen, for instance,
in browsers, from clicking download links. This is expected behaviour, because
in the initial desired capabilities an app bundle id needs to be given, precisely
so that the XCode framework can query its XML source code and in turn provide
the response to the Appium’s WebDriverAgent. This will cause the Python script
to hang, and an error getting the main window is presented to the server backend.

##### GUI Specific Errors

The GUI is currently set up with a dark background due to a bug when integrating with Gooey. The input text would appear as white font onto white background. The workaround was to set the entire background as dark, but this makes it so that the tooltip for the Exerciser arguments is hard to read. This will be changed in a future update.

##### User-level Errors

In its current form, the script expects the list of class arguments separated by
spaces and correctly spelled. In the event that one or more of the classes are
misspelled, the findElements function will return every element on the page and
the algorithm will continue its course.
Loading