Skip to content
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

Add ability to run screenshot tests on multiple devices at once #115

Closed

Conversation

josedlpozo
Copy link
Contributor

@josedlpozo josedlpozo commented Oct 1, 2017

I want to run screenshot tests in multiple devices

Issue: #112

What is the goal?

The objective for this PR is to be able to run screenshot tests in multiple devices.

How is it being implemented?

I have added a new python class called DeviceNameCalculator which is intended to calculate a unique device identifier. This is calculated using some adb commands. This device identifier will be the directory where the screenshots will be saved.

I have thought that a reliable device identifier should be based on:

  • Android API Level
  • Google Play Services
  • Screen density
  • Screen height and width

For example, a device with API level 25, Google Play Services, xxhdpi as screen density and a screen of 1920x1600 the name results as API_25_GP_XXHDPI_1920_1600. This will be the folder inside screenshots folder where the screenshots will be saved.

Screenshot filename will be the same as usual.

How can it be tested?

For testing these new changes you can use app-example-androidjunitrunner project from examples folder.

Required changes after this PR

Users should clear screenshot folder and run recordMode in order to create new folders based on the device which they are run.

@josedlpozo
Copy link
Contributor Author

@tdrhq @xiphirx I have closed the another PR and opened this one with the changes required. I wait for your news 😃

Thank u!

@j3susalonso
Copy link

Any updates?? Seems like a nice feature to have... @tdrhq @xiphirx

@xiphirx
Copy link
Contributor

xiphirx commented Oct 10, 2017

I will get to this PR soon, it changes a lot of things and I'm currently busy syncing things in our internal repo.

Copy link
Contributor

@xiphirx xiphirx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this!

"Screenshot testing is wonderful");

return intent;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets consolidate these three functions into one like:

private Intent given(DetailActivity.Type type, String text) {
  Intent intent = new Intent();
  intent.putExtra(DetailActivity.TYPE, type);
  intent.putExtra(DetailActivity.TEXT, text);

  return intent;
}

and inline the differing parameters in the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

@@ -21,21 +21,24 @@
public class MainActivityTest {

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class, true, false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this change if we want to launch the activity the way we do it now. If we do as before, mainActivityTestSettingsOpen is not a deterministic test.

play_services_text = self._play_services_text()
screen_density_text = self._screen_density_text()
screen_size_text = self._screen_size_text()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should ensure that we have valid values here, and not None. If we receive None from any of the above functions then something is broken, so we should throw an exception here.

try:
density = int(density)
except:
return 'UNKNOWN'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of defaulting to UNKNOWN I would rather let this exception cause an error. _screen_density only looks for a contiguous string of numbers, so this should never fail. In case it does, we shouldn't continue, especially if None was returned.

return 'XHDPI'
elif density in range(321, 481):
return 'XXHDPI'
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this else is unnecessary

if density:
return density.group(0)

def _screen_size_text(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this needs to be a function.

api_version = ''.join(self._api_version().splitlines())
return 'API_' + api_version

def _play_services_text(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets move this function up near _has_play_services


def _has_play_services(self):
output = common.check_output(
[common.get_adb()] + ['shell', 'pm', 'dump', 'com.google.android.gms', '|', 'grep',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check adb shell pm path com.google.android.gms instead. Less output and the same information can be gathered from the output. You will also need to catch an exception here when adb returns a non zero exit code.

play_services_text = self._play_services_text()
screen_density_text = self._screen_density_text()
screen_size_text = self._screen_size_text()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should include the architecture in the name.

[common.get_adb()] + ['shell', 'getprop', 'ro.build.version.sdk'])

def _api_version_text(self):
api_version = ''.join(self._api_version().splitlines())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api_version = int(self._api_version()) ?

@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes

@josedlpozo
Copy link
Contributor Author

Hi @xiphirx! I have already updated the code with your suggestions, please let me know if you have any more doubts!

@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes

@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes

@xiphirx
Copy link
Contributor

xiphirx commented Oct 23, 2017

It seems like you need to re-record the screenshots since the device name calculator takes architecture into account now.

@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes

@josedlpozo
Copy link
Contributor Author

Yeap, I forgot it. I have already included them!

@xiphirx xiphirx changed the title I want to run screenshot tests in multiple devices Add ability to run screenshot tests on multiple devices at once Oct 23, 2017
Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xiphirx has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@@ -445,7 +450,8 @@ def main(argv):
opt_generate_png=opts.get('--generate-png'),
record=opts.get('--record'),
verify=opts.get('--verify'),
adb_puller=SimplePuller(puller_args))
adb_puller=SimplePuller(puller_args),
device_name_calculator=DeviceNameCalculator())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mentioned that you're not using a DeviceNameCalculator by default, but this seems to suggest otherwise. Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where I have mentioned that I'm not using a DeviceNameCalculator by default? I look at this PR and I can't see it.
I think that you are talking about this PR #113, which is closed because you requested some changes and this one was created with the changes instead of changing that.

Did you have a look at this one?
Do you have any news?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay yeah, we definitely need this to default to none, otherwise it's going to break existing users directory structures (and definitely some tools internally at FB :) ). The new format can be made opt-in, perhaps in the future we can turn it on by default if enough people think it's the right move

(@xiphirx is doing most of the review, I was just scanning through this for obvious breakages)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about a NoOpDeviceNameCalculator which returns an empty string when name method is called. This way we don't work with None's. What do you think?

By default, NoOpDeviceNameCalculator is used but users can turn DeviceNameCalculator by setting a true multipleDevices variable in their build.gradle file.

What do you think about creating a new app example project with this multiple devices feature turned on?

Is it okey for you, @xiphirx @tdrhq?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, NoOpDeviceNameCalculator sounds like the right design (but: it might need some more changes because the device_name_calculator right now assumes that it's going into a subdirectory, but the default mode shouldn't go into a subdirectory)

new example app: that would be fantastic, go for it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! I've updated the PR with the changes requested. I took the freedom to write the example app in Kotlin.

About this 'device_name_calculator right now assumes that it's going into a subdirectory, but the default mode shouldn't go into a subdirectory', I think that you're talking about the change in recorder.py from os.mkdir to os.makedirs. Makedirs works also if you want to create a just one directory nor only with multiple directories.

@xiphirx @tdrhq If you have any news, please let me know!

@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes, changes since last import

@xiphirx
Copy link
Contributor

xiphirx commented Nov 28, 2017

Hey @josedlpozo it seems like there are some merge conflicts that need to be resolved.

@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes, changes since last import

@josedlpozo
Copy link
Contributor Author

Hey @xiphirx conflicts are resolved. Do you have any news??

@xiphirx
Copy link
Contributor

xiphirx commented Nov 29, 2017

Hi @josedlpozo I'm currently trying to import your code internally, it takes a bit to do so. After, I need to test a few things and then I should be able to merge it in.

Copy link
Contributor

@xiphirx xiphirx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few things that need to be removed / reverted.


# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file.


import java.io.Serializable

sealed class MessageType : Serializable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets not implement Serializable here and instead just use plain Ints

fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please delete this file.

def usage():
print("usage: ./scripts/screenshot_tests/pull_screenshots com.facebook.apk.name.tests [--generate-png]", file=sys.stderr)
print ( "usage: ./scripts/screenshot_tests/pull_screenshots com.facebook.apk.name.tests [--generate-png]", file=sys.stderr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert this please

@@ -171,7 +180,7 @@ def write_view_hierarchy_overlay_nodes(hierarchy, html, parent_id):
while not to_output.empty():
node = to_output.get()
left = node[KEY_LEFT]
top = node[KEY_TOP]
top = node[KEY_TOP]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert this please

- Revert some changes requested by @xiphirx
- Remove serializable and mvoe to Ints
@facebook-github-bot
Copy link

@josedlpozo has updated the pull request. View: changes, changes since last import

@xiphirx
Copy link
Contributor

xiphirx commented Nov 30, 2017

Thanks again @josedlpozo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants