-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Element screenshot (Android) #2222
Conversation
detox/android/detox/src/main/java/com/wix/detox/common/ViewScreenshot.kt
Outdated
Show resolved
Hide resolved
|
||
public static ViewInteraction perform(ViewInteraction interaction, ViewAction action) { | ||
return interaction.perform(action); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This never made sense! interaction.peform()
always returns the interaction itself. What was the point here, to begin with? Effectively, invoke
cannot parse an object, and therefore the final returned invoke-result has always been null
.
With this new implementation, it's possible to return a result from the action
itself. In the future, this should be useful for implementing getAttributes()
@rotemmiz
class Interaction { | ||
constructor(invocationManager) { | ||
this._call = undefined; | ||
this._invocationManager = invocationManager; | ||
} | ||
|
||
async execute() { | ||
await this._invocationManager.execute(this._call); | ||
const resultObj = await this._invocationManager.execute(this._call); | ||
return resultObj ? resultObj.result : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This enables effectively using the result returned from invoke action on the device
await e.element(e.by.id('ScrollView799')).swipe('left', 'fast', 0.9); | ||
await e.element(e.by.id('ScrollView799')).swipe('right', 'slow', 0.9); | ||
await e.element(e.by.id('ScrollView799')).atIndex(1); | ||
describe('element interactions', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more boyscouting here than really related to this feature, except that I wanted more specific tests related to this specific interaction (screenshot taking)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very good IMO, the only thing that bothers me is the API . element(matcher).takeScreenshot()
is confusing with device.taakeScreenshot()
.
Also, this is a view bitmap, not a screen-shot, so I suggest chaging the name to element(matcher).takeViewBitamp()
, element(matcher).getViewBitamp()
, element(matcher).captureView()
or something similar.
@rotemmiz how about |
Also great |
Duly noted! |
@rotemmiz done! |
@rotemmiz As opposed to screenshot, which is not a bitmap? “Bitmap” is a bad terminology. It should be “snapshot”. |
That's why I suggested that |
I think we should be using the artifact system for this, and so “bitmap” is a very specific term. Images won’t be bmp, probably. |
View is also something we avoid everywhere, so we shouldn’t use that. |
detox/src/utils/easy-file-io.js
Outdated
function saveRawBase64Data(dataBase64, { filePath, fileSuffix }) { | ||
const _filePath = filePath || tempfile(fileSuffix); | ||
const data = Buffer.from(dataBase64, 'base64'); | ||
fs.writeFileSync(_filePath, data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you clean up temporary files after the test session ends?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any good sense to bother with Buffer conversions?
fs.writeFileSync('hello.txt', 'SGVsbG8sIHdvcmxkIQ==', 'base64')
fs.readFileSync('hello.txt', 'utf8'); // 'Hello, world!'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😻 😻 😻
@rotemmiz I tend to agree with @LeoNatan:
As per snapshot - I'm highly against that because the term is used in snapshot testing. Moreover, this feature in particular, aims at providing a means for app-devs to take screenshots in order to perform snapshot tests. We should really avoid the term here. |
So the feature is intended for snapshot testing, but snapshot is not a good term? I don't understand. |
Apple uses "screenshot" terminology: https://developer.apple.com/documentation/xctest/xcuiscreenshotproviding https://developer.apple.com/documentation/xctest/xcuiscreenshot |
detox/src/utils/easy-file-io.test.js
Outdated
uut = require('./easy-file-io'); | ||
}); | ||
|
||
it('should save raw data to a given file', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do you use this scenario, Amit? @d4vidi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I'll remove support for that.
detox/src/android/expect.js
Outdated
async captureBitmap() { | ||
// TODO this should be moved to a lower-layer handler of this use-case | ||
const resultBase64 = await new ActionInteraction(this._invocationManager, this, new CaptureBitmapAction()).execute(); | ||
return ezFile.saveRawBase64Data(resultBase64, { fileSuffix: '.detox.element.png' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could not we just move detox/src/artifacts/utils/temporaryPath.js
to detox/src/utils
, import it:
const temporaryPath = require('../utils/temporaryPath');
and just use in captureBitmap
:
const resultBase64 = await new ActionInteraction(this._invocationManager, this, new CaptureBitmapAction()).execute();
const pngPath = temporaryPath.for.png();
await fs.writeFile(pngPath, resultBase64, 'base64');
return pngPath;
and not create yet another mini-module, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered doing that a number of times, but temporaryPath
seemed very artifact oriented, seeing that the API set basically pairs 1:1 with the artifacts types we have (image, log, video, perf recording). If you find it apt nevertheless, I will use it here.
The mini-module was a compromise as we do not have a lower-layer logic-provider for this, just yet. I wanted to keep the logic here down to the bare minimum. 'Makes it easier to test (when in a separate module), too. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just don't see what it is here to test. 🤷♂️
The test idea here would be to check whether we write a received (mocked) base64 string to some temporary file.
If we extract logic to a separate file, it feels like we are going to write very similar tests (hence, 2 times the same) - for expect.js
's takeScreenshot
and that minimodule.
I don't feel like expanding the test logic by extra 3-4 lines is going to hurt its legibility:
ezFileIo = require('../utils/easy-file-io');
ezFileIo.saveRawBase64Data.mockReturnValue('/mock/file/path');
// ...
jest.mock('fs-extra').mock('../utils/temporaryPath');
require('../utils/temporaryPath').for.png.mockReturnValue('mock.png');
// ...
expect(require('fs-extra').writeFile).toHaveBeenCalledWith('mock.png', invokeResultInBase64, 'base64');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@d4vidi, if you are hesitant to change that ez pz unit stuff, I am not happy about it, but whatever you decide - it's up to you.
You have my approval.
8508450
to
6f60834
Compare
@noomorph I'll take your advice on this and get rid of easy-file-io :) |
@d4vidi, 🙏 I sincerely hope I have managed to convey why I considered easy-file-io impractical a bit in a sense of inflating code and unit tests base with minimal benefits in return. Good luck with the field testing of the feature! Hope it works out as our guys expect. |
ad10941
to
10a6171
Compare
@noomorph I generally agree. Again, in this case, this came up trying to work-around the fact we don't have proper abstractions in the expectation/matching realms, namely - with a proper place to put this logic in (I still don't think the But after pointing this out to me, I've come to terms that this can only be worked-out using proper refactoring. |
This reverts commit e56da45.
022da93
to
f3a6ceb
Compare
Description:
Relates to #2012: introduces the fully-blown (almost*) element screenshot taking solution for Android, using an interaction API.
Implementation notes:
After some research, files on Android are a bit of a headache, and really - there's no need for a file on the device. I therefore took @valentynberehovyi's advice and skipped file saving altogether, and instead return the bitmap as the invocation's result (base-64 encoded, of course). I tested this even with an element that takes up almost the entire screen on a full HD device (1920x1080), and it went smooth.
P.S.
PR
2222
, best ever* see caveats in documentation