diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000..15ce66f670 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,46 @@ +pipeline { + agent any + triggers { + cron(env.BRANCH_NAME == 'master' ? 'H 0 * * *' : '') + } + options { + timestamps() + timeout(time: 1, unit: 'HOURS') + } + stages { + stage('test') { + steps { + dir('app/src/androidTest/java/org/mozilla/fenix/syncIntegration') { + sh 'pipenv install' + sh 'pipenv check' + sh 'pipenv run pytest' + } + } + } + } + post { + always { + script { + publishHTML(target: [ + allowMissing: false, + alwaysLinkToLastBuild: true, + keepAll: true, + reportDir: '/Users/synctesting/.jenkins/workspace/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SyncIntegrationTests/results', + reportFiles: 'index.html', + reportName: 'HTML Report']) + } + } + + failure { + slackSend( + color: 'danger', + message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + } + + fixed { + slackSend( + color: 'good', + message: "FIXED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + } + } +} diff --git a/app/build.gradle b/app/build.gradle index 468f18a258..17ecfbec24 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -125,6 +125,12 @@ android { flavorDimensions "engine" + sourceSets { + androidTest { + resources.srcDirs += ['src/androidTest/resources'] + } + } + productFlavors { geckoNightly { dimension "engine" diff --git a/app/src/androidTest/java/org/mozilla/fenix/Experiments.kt b/app/src/androidTest/java/org/mozilla/fenix/Experiments.kt new file mode 100644 index 0000000000..b86c530920 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/Experiments.kt @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix + +import android.content.Context +import mozilla.components.service.fretboard.ExperimentDescriptor + +const val EXPERIMENTS_JSON_FILENAME = "experiments.json" +const val EXPERIMENTS_BASE_URL = "https://firefox.settings.services.mozilla.com/v1" +const val EXPERIMENTS_BUCKET_NAME = "main" +// collection name below, see https://bugzilla.mozilla.org/show_bug.cgi?id=1523395 for ownership details +const val EXPERIMENTS_COLLECTION_NAME = "fenix-experiments" + +object Experiments { + val AATestDescriptor = ExperimentDescriptor("AAtest") + // application services flag to disable the Firefox Sync syncManager + val asFeatureSyncDisabled = ExperimentDescriptor("asFeatureSyncDisabled") + // application services flag to disable Firefox Accounts pairing button. + val asFeatureFxAPairingDisabled = ExperimentDescriptor("asFeatureFxAPairingDisabled") + // application services flag to disable Firefox Accounts WebChannel integration. + val asFeatureWebChannelsDisabled = ExperimentDescriptor("asFeatureWebChannelsDisabled") +} + +val Context.app: FenixApplication + get() = applicationContext as FenixApplication + +fun Context.isInExperiment(descriptor: ExperimentDescriptor): Boolean = + if (descriptor.name == "asFeatureWebChannelsDisabled") + true + else + app.fretboard.isInExperiment(this, descriptor) diff --git a/app/src/androidTest/java/org/mozilla/fenix/components/FxaServer.kt b/app/src/androidTest/java/org/mozilla/fenix/components/FxaServer.kt new file mode 100644 index 0000000000..412c5a9ee1 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/components/FxaServer.kt @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components + +import android.content.Context +import mozilla.components.service.fxa.ServerConfig + +/** + * Utility to configure Firefox Account stage servers. + */ + +object FxaServer { + const val CLIENT_ID = "a2270f727f45f648" + const val REDIRECT_URL = "https://accounts.stage.mozaws.net/oauth/success/$CLIENT_ID" + + @Suppress("UNUSED_PARAMETER") + fun redirectUrl(context: Context) = REDIRECT_URL + + @Suppress("UNUSED_PARAMETER") + fun config(context: Context): ServerConfig { + return ServerConfig.dev(CLIENT_ID, REDIRECT_URL) + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile new file mode 100644 index 0000000000..5f1eeed6b4 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile @@ -0,0 +1,22 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +fxapom = "*" +mozdownload = "*" +mozinstall = "*" +mozprofile = "*" +mozrunner = "*" +mozversion = "*" +pytest = "*" +pytest-fxa = "*" +pytest-html = "*" +pytest-metadata = "*" +requests = "*" + +[dev-packages] + +[requires] +python_version = "2.7" diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock new file mode 100644 index 0000000000..8400159cae --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock @@ -0,0 +1,561 @@ +{ + "_meta": { + "hash": { + "sha256": "112a12fa2e9e8117b399b60a49b4c8799a614ef655992640c95149bf95f33e8b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "2.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asn1crypto": { + "hashes": [ + "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", + "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + ], + "version": "==0.24.0" + }, + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "blessings": { + "hashes": [ + "sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d", + "sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3", + "sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e" + ], + "version": "==1.7" + }, + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "cffi": { + "hashes": [ + "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", + "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", + "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", + "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", + "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", + "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", + "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", + "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", + "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", + "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", + "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", + "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", + "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", + "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", + "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", + "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", + "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", + "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", + "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", + "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", + "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", + "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", + "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", + "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", + "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", + "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", + "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", + "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201" + ], + "version": "==1.12.3" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "configparser": { + "hashes": [ + "sha256:8be81d89d6e7b4c0d4e44bcc525845f6da25821de80cb5e06e7e0238a2899e32", + "sha256:da60d0014fd8c55eb48c1c5354352e363e2d30bbf7057e5e171a468390184c75" + ], + "markers": "python_version < '3'", + "version": "==3.7.4" + }, + "contextlib2": { + "hashes": [ + "sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", + "sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00" + ], + "markers": "python_version < '3'", + "version": "==0.5.5" + }, + "cryptography": { + "hashes": [ + "sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c", + "sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643", + "sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216", + "sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799", + "sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a", + "sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9", + "sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc", + "sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8", + "sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53", + "sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1", + "sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609", + "sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292", + "sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e", + "sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6", + "sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed", + "sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d" + ], + "version": "==2.7" + }, + "enum34": { + "hashes": [ + "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", + "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", + "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", + "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" + ], + "markers": "python_version < '3'", + "version": "==1.1.6" + }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "markers": "python_version < '3.0'", + "version": "==1.0.2" + }, + "fxapom": { + "hashes": [ + "sha256:56fdff0a0f0ea58831337e3a859971f98c59fd028ebc14baa8e37ae08a40efa0", + "sha256:5fb902afaaa9d9b82b5d1d54b9e19f1f4c9be128deb3b0e0ac82a9303f76000f" + ], + "index": "pypi", + "version": "==1.10.2" + }, + "hawkauthlib": { + "hashes": [ + "sha256:935878d3a75832aa76f78ddee13491f1466cbd69a8e7e4248902763cf9953ba9", + "sha256:effd64a2572e3c0d9090b55ad2180b36ad50e7760bea225cb6ce2248f421510d" + ], + "version": "==2.0.0" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "importlib-metadata": { + "hashes": [ + "sha256:a9f185022cfa69e9ca5f7eabfd5a58b689894cb78a11e3c8c89398a8ccbb8e7f", + "sha256:df1403cd3aebeb2b1dcd3515ca062eecb5bd3ea7611f18cba81130c68707e879" + ], + "version": "==0.17" + }, + "ipaddress": { + "hashes": [ + "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", + "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" + ], + "markers": "python_version < '3'", + "version": "==1.0.22" + }, + "more-itertools": { + "hashes": [ + "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", + "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", + "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + ], + "markers": "python_version <= '2.7'", + "version": "==5.0.0" + }, + "mozdevice": { + "hashes": [ + "sha256:a7b582331448f28c9f44d5a113a152537e5666b0f5ce1052dc569ab516ec99a8", + "sha256:ec618e0c8000663de99af076f9d5ffdf91eaa81105b75b42a2dbcdb0248c2acb" + ], + "version": "==3.0.1" + }, + "mozdownload": { + "hashes": [ + "sha256:1664b0bf48eab69fafa73d3fc4dc19f4c66dfc21045fab3ca76a29b3eeb31702", + "sha256:d861936c2efcc7620858a097907bfaba5d6d114867b6633e4301da9263627819" + ], + "index": "pypi", + "version": "==1.26.0" + }, + "mozfile": { + "hashes": [ + "sha256:22a43f3bc320c3bda27e54b293c23a51660f4a00ec6959ab70ca6136d702f578", + "sha256:7355643fca705f43d9412d444fb59588fbbd25f0d1e7592b9482f84e3d23bc8e" + ], + "version": "==2.0.0" + }, + "mozinfo": { + "hashes": [ + "sha256:299827c42d54ebb3c3547e299846abd15d22ff677cf6c1adbf54faeb9f024832", + "sha256:4525c26350fb85c26b38c5f853a19f47b17b49a74de363d285d54258972a4cbc" + ], + "version": "==1.1.0" + }, + "mozinstall": { + "hashes": [ + "sha256:219ba7c51308433487b4f30a2615cb9b3ecd40a76b9faf41cf1b1b005bb5dda7" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "mozlog": { + "hashes": [ + "sha256:70ad145949539a3229ffb9b9785058366f2645371a2371cf2ad7c8f2582893ae", + "sha256:d294a71433cca3c873176a87df2ebe8bd632caf63aea4c7c47c1b69700c83dd5" + ], + "version": "==4.1" + }, + "mozprocess": { + "hashes": [ + "sha256:6c984ab251bf0b1d6eef7e4fff87c2c2b60f20953e75eae566cb2d239f39806a", + "sha256:a0fd8367e663d3cac74ee46bffa789667bc8d52f242d81a14522205fa6650cb2" + ], + "version": "==1.0.0" + }, + "mozprofile": { + "hashes": [ + "sha256:65cd29ab2925e40130935b144de80d23270a1bc66bf312b813942b1ba6eb7a76", + "sha256:94a0e14fcf357a90c42418edd414e837ff63bab876ccd51b9a7810d6f3c9fe2d" + ], + "index": "pypi", + "version": "==2.2.0" + }, + "mozrunner": { + "hashes": [ + "sha256:3d3d48ceff333262d4f648314d5c7ac8e1998bf7ed08a7f96ac2a08558958c7b", + "sha256:c98c7a3cabbe7b63348f7c495ed40e75c3946c01896a1bec756e782b30a7b7d5" + ], + "index": "pypi", + "version": "==7.4.0" + }, + "mozterm": { + "hashes": [ + "sha256:b1e91acec188de07c704dbb7b0100a7be5c1e06567b3beb67f6ea11d00a483a4", + "sha256:f5eafa25c23d391e2a2bb1dd45ee928fc9e3c811977a3856b5a5a0778011053c" + ], + "version": "==1.0.0" + }, + "mozversion": { + "hashes": [ + "sha256:35911badaaf02715e56c6062379688724e7afeffc2d25be8567312d24054cdd4", + "sha256:65f41d7dc14002f83d8f147c82ca34f7213ad07065d250939daaeeb3787dc0fa" + ], + "index": "pypi", + "version": "==2.1.0" + }, + "packaging": { + "hashes": [ + "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", + "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" + ], + "version": "==19.0" + }, + "pathlib2": { + "hashes": [ + "sha256:25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742", + "sha256:5887121d7f7df3603bca2f710e7219f3eca0eb69e0b7cc6e0a022e155ac931a7" + ], + "markers": "python_version < '3.6'", + "version": "==2.3.3" + }, + "pluggy": { + "hashes": [ + "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", + "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + ], + "version": "==0.12.0" + }, + "progressbar2": { + "hashes": [ + "sha256:9ecb2d35edaa30ed86eb92fb0135eb19a6ef9b3657be4350cc3d0d3563e6af65", + "sha256:c086fc1f718839c0ec95caaeb7bd5a1d0268a8775960b8f9dee06f2a04c84628" + ], + "version": "==3.42.0" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pybrowserid": { + "hashes": [ + "sha256:6c227669e87cc25796ae76f6a0ef65025528c8ad82d352679fa9a3e5663a71e3", + "sha256:8e237d6a2bc9ead849a4472a84d3e6a9309bec99cf8e10d36213710dda8df8ca" + ], + "version": "==0.14.0" + }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "version": "==2.19" + }, + "pyfxa": { + "hashes": [ + "sha256:616689486d8d63956aa40836cffafde6e7590cdeb200badabaaf3c17d5b26cce" + ], + "version": "==0.7.1" + }, + "pyparsing": { + "hashes": [ + "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", + "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" + ], + "version": "==2.4.0" + }, + "pypom": { + "hashes": [ + "sha256:4bdd57fceb72d7e6a3645cf6c9322f490d9cfb5d777eac2c851a3b658b813939", + "sha256:6772ec99f0a21a5bdc8c092007a8c813ed18359e67ed70258bbb233df5e28829" + ], + "version": "==2.2.0" + }, + "pytest": { + "hashes": [ + "sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24", + "sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6" + ], + "index": "pypi", + "version": "==4.5.0" + }, + "pytest-fxa": { + "hashes": [ + "sha256:778dfdb019f1e0af8744704fe5f7ac5c08fd5d45ff054023b0a18d5f99d737f1", + "sha256:b75967e74e9b2f3ffa5558421fdf61c7fff5948fc9d7e357e7147c682988ecc1" + ], + "index": "pypi", + "version": "==1.4.0" + }, + "pytest-html": { + "hashes": [ + "sha256:648b7ba1d6035cc021d607e9d44f4dc06e916bdb04e09572dd04fb82eecab9ed", + "sha256:a7c65cdd9d5e4d09cef2f500ca801f80c1110204f24e5b84d019c6f919b15e9e" + ], + "index": "pypi", + "version": "==1.20.0" + }, + "pytest-metadata": { + "hashes": [ + "sha256:2071a59285de40d7541fde1eb9f1ddea1c9db165882df82781367471238b66ba", + "sha256:c29a1fb470424926c63154c1b632c02585f2ba4282932058a71d35295ff8c96d" + ], + "index": "pypi", + "version": "==1.8.0" + }, + "python-utils": { + "hashes": [ + "sha256:34aaf26b39b0b86628008f2ae0ac001b30e7986a8d303b61e1357dfcdad4f6d3", + "sha256:e25f840564554eaded56eaa395bca507b0b9e9f0ae5ecb13a8cb785305c56d25" + ], + "version": "==2.3.0" + }, + "redo": { + "hashes": [ + "sha256:36784bf8ae766e14f9db0e377ccfa02835d648321d2007b6ae0bf4fd612c0f94", + "sha256:71161cb0e928d824092a5f16203939bbc0867ce4c4685db263cf22c3ae7634a8" + ], + "version": "==2.0.3" + }, + "requests": { + "hashes": [ + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + ], + "index": "pypi", + "version": "==2.22.0" + }, + "scandir": { + "hashes": [ + "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", + "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022", + "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f", + "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f", + "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae", + "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173", + "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4", + "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32", + "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188", + "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d", + "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac" + ], + "markers": "python_version < '3.5'", + "version": "==1.10.0" + }, + "selenium": { + "hashes": [ + "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c", + "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d" + ], + "version": "==3.141.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "treeherder-client": { + "hashes": [ + "sha256:4020809424384574277232023c78bcee436ec5474020b4430b4770f0ddd8bba3", + "sha256:db25150480d0501c79b72966899e5c901a5a625e12739389f6bee03273e1d002" + ], + "version": "==5.0.0" + }, + "urllib3": { + "hashes": [ + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + ], + "version": "==1.25.3" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + }, + "webob": { + "hashes": [ + "sha256:05aaab7975e0ee8af2026325d656e5ce14a71f1883c52276181821d6d5bf7086", + "sha256:36db8203c67023d68c1b00208a7bf55e3b10de2aa317555740add29c619de12b" + ], + "version": "==1.8.5" + }, + "zipp": { + "hashes": [ + "sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", + "sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3" + ], + "version": "==0.5.1" + }, + "zope.component": { + "hashes": [ + "sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55", + "sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4" + ], + "version": "==4.5" + }, + "zope.deferredimport": { + "hashes": [ + "sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5", + "sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec" + ], + "version": "==4.3" + }, + "zope.deprecation": { + "hashes": [ + "sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df", + "sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113" + ], + "version": "==4.4.0" + }, + "zope.event": { + "hashes": [ + "sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf", + "sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7" + ], + "version": "==4.4" + }, + "zope.hookable": { + "hashes": [ + "sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370", + "sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab", + "sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220", + "sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b", + "sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801", + "sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c", + "sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83", + "sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15", + "sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893", + "sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2" + ], + "version": "==4.2.0" + }, + "zope.interface": { + "hashes": [ + "sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c", + "sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b", + "sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02", + "sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f", + "sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5", + "sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375", + "sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487", + "sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2", + "sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0", + "sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b", + "sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63", + "sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39", + "sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745", + "sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc", + "sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2", + "sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa", + "sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1", + "sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc", + "sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98", + "sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97", + "sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab", + "sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127", + "sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d", + "sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe", + "sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891", + "sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1", + "sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b", + "sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966", + "sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317" + ], + "version": "==4.6.0" + }, + "zope.proxy": { + "hashes": [ + "sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b", + "sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd", + "sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb", + "sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2", + "sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5", + "sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475", + "sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe", + "sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3", + "sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08", + "sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb", + "sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed" + ], + "version": "==4.3.1" + } + }, + "develop": {} +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt new file mode 100644 index 0000000000..3e0f633af6 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.syncintegration + +import android.os.SystemClock.sleep +import android.widget.EditText + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.pressImeActionButton +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText + +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.helpers.HomeActivityTestRule +import org.mozilla.fenix.ui.robots.homeScreen + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.hamcrest.Matchers.allOf +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.TestAssetHelper + +@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") +class SyncIntegrationTest { + val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + @get:Rule + val activityTestRule = HomeActivityTestRule() + + // History item Desktop -> Fenix + @Test + fun checkHistoryFromDesktopTest() { + signInFxSync() + tapReturnToPreviousApp() + homeScreen { + }.openThreeDotMenu { + }.openLibrary { + }.openHistory { } + historyAfterSyncIsShown() + } + /* These tests will be running in the future + // once the test above runs successfully and + // the environment is stable + + // Bookmark item Desktop -> Fenix + @Test + fun checkBookmarkFromDesktopTest() { + signInFxSync() + tapReturnToPreviousApp() + homeScreen { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { } + bookmarkAfterSyncIsShown() + } + + // History item Fenix -> Desktop + @Test + fun checkBookmarkFromDeviceTest() { + tapInToolBar() + typeInToolBar() + seeBookmark() + mDevice.pressBack() + signInFxSync() + } + + // Bookmark item Fenix -> Desktop + @Test + fun checkHistoryFromDeviceTest() { + tapInToolBar() + typeInToolBar() + sleep(TestAssetHelper.waitingTime) + mDevice.pressBack() + signInFxSync() + } + */ + + // Useful functions for the tests + fun typeEmail() { + val emailInput = mDevice.findObject(UiSelector() + .instance(0) + .className(EditText::class.java)) + emailInput.waitForExists(TestAssetHelper.waitingTime) + + val emailAddress = javaClass.classLoader.getResource("email.txt").readText() + emailInput.setText(emailAddress) + } + + fun tapOnContinueButton() { + val continueButton = mDevice.findObject(By.res("submit-btn")) + continueButton.clickAndWait(Until.newWindow(), TestAssetHelper.waitingTime) + } + + fun typePassowrd() { + val passwordInput = mDevice.findObject(UiSelector() + .instance(0) + .className(EditText::class.java)) + + val passwordValue = javaClass.classLoader.getResource("password.txt").readText() + passwordInput.setText(passwordValue) + } + + fun tapOnSignIn() { + mDevice.wait(Until.findObjects(By.text("Sign in")), TestAssetHelper.waitingTime) + // Let's tap on enter, sometimes depending on the device the sign in button is + // hidden by the keyboard + mDevice.pressEnter() + } + + fun typeInToolBar() { + awesomeBar().perform(replaceText("example.com"), + pressImeActionButton()) + } + + fun historyAfterSyncIsShown() { + val historyEntry = mDevice.findObject(By.text("http://www.example.com/")) + historyEntry.isEnabled() + } + + fun bookmarkAfterSyncIsShown() { + val bookmarkyEntry = mDevice.findObject(By.text("Example Domain")) + bookmarkyEntry.isEnabled() + } + + fun seeBookmark() { + mDevice.wait(Until.findObjects(By.text("Bookmark")), TestAssetHelper.waitingTime) + val bookmarkButton = mDevice.findObject(By.text("Bookmark")) + bookmarkButton.click() + } + + fun tapReturnToPreviousApp() { + mDevice.wait(Until.findObjects(By.text("Connected")), TestAssetHelper.waitingTime) + + val settingsLabel = mDevice.wait(Until.findObject(By.text("Settings")), TestAssetHelper.waitingTime) + settingsLabel.isClickable() + + mDevice.wait(Until.findObjects(By.desc("Navigate up")), TestAssetHelper.waitingTime) + val backButton = mDevice.findObject(By.desc("Navigate up")) + backButton.click() + } + + fun signInFxSync() { + homeScreen { + }.openThreeDotMenu { + verifySettingsButton() + }.openSettings {} + settingsAccount() + useEmailInsteadButton() + + typeEmail() + tapOnContinueButton() + typePassowrd() + sleep(TestAssetHelper.waitingTime) + tapOnSignIn() + } +} + +fun settingsAccount() = onView(allOf(withText("Turn on Sync"))).perform(click()) +fun tapInToolBar() = onView(withId(org.mozilla.fenix.R.id.toolbar_wrapper)) +fun awesomeBar() = onView(withId(org.mozilla.fenix.R.id.mozac_browser_toolbar_edit_url_view)) +fun useEmailInsteadButton() = onView(withId(R.id.signInEmailButton)).perform(click()) diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/__init__.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/adbrun.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/adbrun.py new file mode 100644 index 0000000000..b89cbd8b9b --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/adbrun.py @@ -0,0 +1,16 @@ +import logging +import subprocess +import os + +logging.getLogger(__name__).addHandler(logging.NullHandler()) + + +class ADBrun(object): + binary = 'adbrun' + logger = logging.getLogger() + + def launch(self): + # First close sim if any then launch + os.system('~/Library/Android/sdk/platform-tools/adb devices | grep emulator | cut -f1 | while read line; do ~/Library/Android/sdk/platform-tools/adb -s $line emu kill; done') + # Then launch sim + os.system("sh launchSimScript.sh") diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/conftest.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/conftest.py new file mode 100644 index 0000000000..a80590bff3 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/conftest.py @@ -0,0 +1,167 @@ +import io +import json +import os +import time + +from mozdownload import DirectScraper, FactoryScraper +from mozprofile import Profile +import mozinstall +import mozversion +import pytest +import requests + +from tps import TPS +from gradlewbuild import GradlewBuild + +here = os.path.dirname(__file__) + +@pytest.fixture(scope='session') +def firefox(pytestconfig, tmpdir_factory): + binary = os.getenv('MOZREGRESSION_BINARY', + pytestconfig.getoption('firefox')) + if binary is None: + cache_dir = str(pytestconfig.cache.makedir('firefox')) + scraper = FactoryScraper('daily', destination=cache_dir) + build_path = scraper.download() + install_path = str(tmpdir_factory.mktemp('firefox')) + install_dir = mozinstall.install(src=build_path, dest=install_path) + binary = mozinstall.get_binary(install_dir, 'firefox') + version = mozversion.get_version(binary) + if hasattr(pytestconfig, '_metadata'): + pytestconfig._metadata.update(version) + return binary + + +@pytest.fixture +def firefox_log(pytestconfig, tmpdir): + firefox_log = str(tmpdir.join('firefox.log')) + pytestconfig._firefox_log = firefox_log + yield firefox_log + + +@pytest.fixture(scope='session') +def tps_addon(pytestconfig, tmpdir_factory): + path = pytestconfig.getoption('tps') + if path is not None: + return path + task_url = 'https://index.taskcluster.net/v1/task/' \ + 'gecko.v2.mozilla-central.latest.firefox.addons.tps' + task_id = requests.get(task_url).json().get('taskId') + cache_dir = str(pytestconfig.cache.makedir('tps-{}'.format(task_id))) + addon_url = 'https://queue.taskcluster.net/v1/task/' \ + '{}/artifacts/public/tps.xpi'.format(task_id) + scraper = DirectScraper(addon_url, destination=cache_dir) + return scraper.download() + + +@pytest.fixture +def tps_config(fxa_account, monkeypatch): + monkeypatch.setenv('FXA_EMAIL', fxa_account.email) + monkeypatch.setenv('FXA_PASSWORD', fxa_account.password) + with open ("/Users/synctesting/.jenkins/workspace/fenix/app/src/androidTest/resources/email.txt", "w") as f: + f.write(fxa_account.email) + + with open ("/Users/synctesting/.jenkins/workspace/fenix/app/src/androidTest/resources/password.txt", "w") as f: + f.write(fxa_account.password) + + yield {'fx_account': { + 'username': fxa_account.email, + 'password': fxa_account.password} + } + +@pytest.fixture +def tps_log(pytestconfig, tmpdir): + tps_log = str(tmpdir.join('tps.log')) + pytestconfig._tps_log = tps_log + yield tps_log + + +@pytest.fixture +def tps_profile(pytestconfig, tps_addon, tps_config, tps_log, fxa_urls): + preferences = { + 'app.update.enabled': False, + 'browser.dom.window.dump.enabled': True, + 'browser.onboarding.enabled': False, + 'browser.sessionstore.resume_from_crash': False, + 'browser.shell.checkDefaultBrowser': False, + 'browser.startup.homepage_override.mstone': 'ignore', + 'browser.startup.page': 0, + 'browser.tabs.warnOnClose': False, + 'browser.warnOnQuit': False, + 'datareporting.policy.dataSubmissionEnabled': False, + # 'devtools.chrome.enabled': True, + # 'devtools.debugger.remote-enabled': True, + 'engine.bookmarks.repair.enabled': False, + 'extensions.autoDisableScopes': 10, + 'extensions.legacy.enabled': True, + 'extensions.update.enabled': False, + 'extensions.update.notifyUser': False, + # While this line is commented prod is launched instead of stage + 'identity.fxaccounts.autoconfig.uri': fxa_urls['content'], + 'testing.tps.skipPingValidation': True, + 'services.sync.firstSync': 'notReady', + 'services.sync.lastversion': '1.0', + 'services.sync.log.appender.console': 'Trace', + 'services.sync.log.appender.dump': 'Trace', + 'services.sync.log.appender.file.level': 'Trace', + 'services.sync.log.appender.file.logOnSuccess': True, + 'services.sync.log.logger': 'Trace', + 'services.sync.log.logger.engine': 'Trace', + 'services.sync.testing.tps': True, + 'testing.tps.logFile': tps_log, + 'toolkit.startup.max_resumed_crashes': -1, + 'tps.config': json.dumps(tps_config), + 'tps.seconds_since_epoch': int(time.time()), + 'xpinstall.signatures.required': False + } + profile = Profile(addons=[tps_addon], preferences=preferences) + pytestconfig._profile = profile.profile + yield profile + +@pytest.fixture +def tps(firefox, firefox_log, monkeypatch, pytestconfig, tps_log, tps_profile): + yield TPS(firefox, firefox_log, tps_log, tps_profile) + +@pytest.fixture +def gradlewbuild_log(pytestconfig, tmpdir): + gradlewbuild_log = str(tmpdir.join('gradlewbuild.log')) + pytestconfig._gradlewbuild_log = gradlewbuild_log + yield gradlewbuild_log + +@pytest.fixture +def gradlewbuild(fxa_account, monkeypatch, gradlewbuild_log): + monkeypatch.setenv('FXA_EMAIL', fxa_account.email) + monkeypatch.setenv('FXA_PASSWORD', fxa_account.password) + yield GradlewBuild(gradlewbuild_log) + +def pytest_addoption(parser): + parser.addoption('--firefox', help='path to firefox binary (defaults to ' + 'downloading latest nightly build)') + parser.addoption('--tps', help='path to tps add-on (defaults to ' + 'downloading latest nightly build)') + +@pytest.mark.hookwrapper +def pytest_runtest_makereport(item, call): + outcome = yield + report = outcome.get_result() + extra = getattr(report, 'extra', []) + pytest_html = item.config.pluginmanager.getplugin('html') + profile = getattr(item.config, '_profile', None) + if profile is not None and os.path.exists(profile): + # add sync logs to HTML report + for root, _, files in os.walk(os.path.join(profile, 'weave', 'logs')): + for f in files: + path = os.path.join(root, f) + if pytest_html is not None: + with io.open(path, 'r', encoding='utf8') as f: + extra.append(pytest_html.extras.text(f.read(), 'Sync')) + report.sections.append(('Sync', 'Log: {}'.format(path))) + for log in ('Firefox', 'TPS', 'GradlewBuild'): + attr = '_{}_log'.format(log.lower()) + path = getattr(item.config, attr, None) + if path is not None and os.path.exists(path): + if pytest_html is not None: + with io.open(path, 'r', encoding='utf8') as f: + extra.append(pytest_html.extras.text(f.read(), log)) + report.sections.append((log, 'Log: {}'.format(path))) + report.extra = extra diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py new file mode 100644 index 0000000000..a02d8743ed --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py @@ -0,0 +1,35 @@ +import logging +import os +import subprocess + +from adbrun import ADBrun + +here = os.path.dirname(__file__) +logging.getLogger(__name__).addHandler(logging.NullHandler()) + + +class GradlewBuild(object): + binary = './gradlew' + logger = logging.getLogger() + adbrun = ADBrun() + + def __init__(self, log): + self.log = log + + def test(self, identifier): + self.adbrun.launch() + # Change path accordingly to go to root folder to run gradlew + os.chdir('../../../../../../../..') + args = './gradlew ' + 'app:connectedGeckoNightlyDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.mozilla.fenix.syncintegration.SyncIntegrationTest#{}'.format(identifier) + + self.logger.info('Running: {}'.format(' '.join(args))) + try: + out = subprocess.check_output( + args, shell=True) + except subprocess.CalledProcessError as e: + out = e.output + raise + finally: + with open(self.log, 'w') as f: + f.writelines(out) + diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/launchSimScript.sh b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/launchSimScript.sh new file mode 100755 index 0000000000..648397f0ec --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/launchSimScript.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e + +echo "Waiting emulator is ready..." +~/Library/Android/sdk/emulator/emulator -avd Pixel_3_API_28 -wipe-data -no-boot-anim -screen no-touch & + +bootanim="" +failcounter=0 +timeout_in_sec=360 + +until [[ "$bootanim" =~ "stopped" ]]; do + bootanim=`~/Library/Android/sdk/platform-tools/adb -e shell getprop init.svc.bootanim 2>&1 &` + if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline" + || "$bootanim" =~ "running" ]]; then + let "failcounter += 1" + echo "Waiting for emulator to start" + if [[ $failcounter -gt timeout_in_sec ]]; then + echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator" + exit 1 + fi + fi + sleep 1 +done + +echo "Emulator is ready" +sleep 10 diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/pytest.ini b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/pytest.ini new file mode 100644 index 0000000000..fda734a9f8 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +addopts = --verbose --html=results/index.html +log_cli = true +log_cli_level = info \ No newline at end of file diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js new file mode 100644 index 0000000000..a6a42aa6d0 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in strict JSON format, as it will get parsed by the Python + * testrunner (no single quotes, extra comma's, etc). + */ +EnableEngines(["bookmarks"]); + +var phases = { "phase1": "profile1" }; + + +// expected bookmark state +var bookmarksExpected = { +"mobile": [{ + uri: "http://www.example.com/", + title: "Example Domain"}] +}; + +// sync and verify bookmarks +Phase("phase1", [ + [Sync], + [Bookmarks.verify, bookmarksExpected], +]); diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js new file mode 100644 index 0000000000..8b915945e2 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in strict JSON format, as it will get parsed by the Python + * testrunner (no single quotes, extra comma's, etc). + */ +EnableEngines(["history"]); + +var phases = { "phase1": "profile1" }; + + +// expected history state +var historyCreated = [ + { uri: "http://www.example.com/", + visits: [ + { type: 1 , + date: 0 + }, + { type: 2, + date: -1 + } + ] + } +]; + +// sync and verify history +Phase("phase1", [ + [Sync], + [History.add, historyCreated], + [Sync] +]); diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_integration.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_integration.py new file mode 100644 index 0000000000..7a2ab5b94e --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_integration.py @@ -0,0 +1,14 @@ +import os +import sys + + +def test_sync_history_from_desktop(tps, gradlewbuild): + # Running tests + tps.run('test_history.js') + gradlewbuild.test('checkHistoryFromDesktopTest') +''' +# For the future, this way we change the test to run.... +def test_sync_bookmark_from_device(tps, xcodebuild): + gradlewbuild.test('checkBookmarkFromDeviceTest') + tps.run('app/src/androidTest/java/org/mozilla/fenix/ui/SyncIntegrationTests/test_bookmark.js') +''' \ No newline at end of file diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/tps.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/tps.py new file mode 100644 index 0000000000..d61617d3a6 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/tps.py @@ -0,0 +1,52 @@ +import logging +import os + +from mozrunner import FirefoxRunner + +logging.getLogger(__name__).addHandler(logging.NullHandler()) + +TIMEOUT = 60 + + +class TPS(object): + logger = logging.getLogger() + + def __init__(self, firefox, firefox_log, tps_log, profile): + self.firefox = firefox + self.firefox_log = open(firefox_log, 'w') + self.tps_log = tps_log + self.profile = profile + + def _log(self, line): + self.firefox_log.write(line + '\n') + + def run(self, test, phase='phase1', ignore_unused_engines=True): + self.profile.set_preferences({ + 'testing.tps.testFile': os.path.abspath(test), + 'testing.tps.testPhase': phase, + 'testing.tps.ignoreUnusedEngines': ignore_unused_engines, + }) + args = ['-marionette'] + process_args = {'processOutputLine': [self._log]} + self.logger.info('Running: {} {}'.format(self.firefox, ' '.join(args))) + self.logger.info('Using profile at: {}'.format(self.profile.profile)) + runner = FirefoxRunner( + binary=self.firefox, + cmdargs=args, + profile=self.profile, + process_args=process_args) + runner.start(timeout=TIMEOUT) + runner.wait(timeout=TIMEOUT) + self.firefox_log.close() + + with open(self.tps_log) as f: + for line in f.readlines(): + if 'CROSSWEAVE ERROR: ' in line: + raise TPSError(line.partition('CROSSWEAVE ERROR: ')[-1]) + + with open(self.tps_log) as f: + assert 'test phase {}: PASS'.format(phase) in f.read() + + +class TPSError(Exception): + pass diff --git a/app/src/androidTest/resources/email.txt b/app/src/androidTest/resources/email.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/src/androidTest/resources/password.txt b/app/src/androidTest/resources/password.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index c455dd0b2e..4106faef0d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -54,34 +54,24 @@ class BackgroundServices( historyStorage: PlacesHistoryStorage, bookmarkStorage: PlacesBookmarksStorage ) { - companion object { - const val CLIENT_ID = "a2270f727f45f648" - - fun redirectUrl(context: Context) = if (context.isInExperiment(Experiments.asFeatureWebChannelsDisabled)) { - "https://accounts.firefox.com/oauth/success/$CLIENT_ID" - } else { - "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel" - } - } - // // A malformed string is causing crashes. // This will be removed when the string is fixed. See #5552 fun defaultDeviceName(context: Context): String = try { - context.getString( - R.string.default_device_name, - context.getString(R.string.app_name), - Build.MANUFACTURER, - Build.MODEL - ) - } catch (ex: FormatFlagsConversionMismatchException) { - "%s on %s %s".format( - context.getString(R.string.app_name), - Build.MANUFACTURER, - Build.MODEL - ) - } + context.getString( + R.string.default_device_name, + context.getString(R.string.app_name), + Build.MANUFACTURER, + Build.MODEL + ) + } catch (ex: FormatFlagsConversionMismatchException) { + "%s on %s %s".format( + context.getString(R.string.app_name), + Build.MANUFACTURER, + Build.MODEL + ) + } - private val serverConfig = ServerConfig.release(CLIENT_ID, redirectUrl(context)) + private val serverConfig = FxaServer.config(context) private val deviceConfig = DeviceConfig( name = defaultDeviceName(context), type = DeviceType.MOBILE, diff --git a/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt b/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt new file mode 100644 index 0000000000..9161aaff00 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +package org.mozilla.fenix.components + +import android.content.Context +import mozilla.components.service.fxa.ServerConfig +import org.mozilla.fenix.Experiments +import org.mozilla.fenix.isInExperiment + +/** + * Utility to configure Firefox Account servers. + */ + +object FxaServer { + const val CLIENT_ID = "a2270f727f45f648" + const val REDIRECT_URL = "https://accounts.firefox.com/oauth/success/$CLIENT_ID" + + fun redirectUrl(context: Context) = if (context.isInExperiment(Experiments.asFeatureWebChannelsDisabled)) { + REDIRECT_URL + } else { + "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel" + } + + fun config(context: Context): ServerConfig { + return ServerConfig.release(CLIENT_ID, redirectUrl(context)) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/components/Services.kt b/app/src/main/java/org/mozilla/fenix/components/Services.kt index 1638841042..bc049d13fc 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Services.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Services.kt @@ -28,10 +28,12 @@ class Services( private val context: Context, private val accountManager: FxaAccountManager ) { + val fxaRedirectUrl = FxaServer.redirectUrl(context) + val accountsAuthFeature by lazy { FirefoxAccountsAuthFeature( accountManager, - redirectUrl = BackgroundServices.redirectUrl(context) + redirectUrl = fxaRedirectUrl ) { context, authUrl -> CoroutineScope(Dispatchers.Main).launch { val intent = SupportUtils.createAuthCustomTabIntent(context, authUrl) diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index ef150658f9..19e2d959da 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -105,6 +105,6 @@ pref_key_bounce_quick_action pref_key_reader_mode_notification - pref_key_adjust_campaign + pref_key_testing_stage diff --git a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt index c6832317b8..b1262b5b27 100644 --- a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt @@ -50,10 +50,10 @@ class BackgroundServicesTest { val context = mockk(relaxed = true) every { context.isInExperiment(eq(Experiments.asFeatureWebChannelsDisabled)) } returns false - assertEquals("urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel", BackgroundServices.redirectUrl(context)) + assertEquals("urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel", FxaServer.redirectUrl(context)) every { context.isInExperiment(eq(Experiments.asFeatureWebChannelsDisabled)) } returns true - assertEquals("https://accounts.firefox.com/oauth/success/a2270f727f45f648", BackgroundServices.redirectUrl(context)) + assertEquals("https://accounts.firefox.com/oauth/success/a2270f727f45f648", FxaServer.redirectUrl(context)) every { context.isInExperiment(eq(Experiments.asFeatureSyncDisabled)) } returns false var backgroundServices = TestableBackgroundServices(context) diff --git a/automation/taskcluster/androidTest/flank-x86.yml b/automation/taskcluster/androidTest/flank-x86.yml index 5ce00a925c..16c44a42b6 100644 --- a/automation/taskcluster/androidTest/flank-x86.yml +++ b/automation/taskcluster/androidTest/flank-x86.yml @@ -37,6 +37,9 @@ gcloud: - /sdcard/screenshots performance-metrics: true + test-targets: + - package org.mozilla.fenix.ui + device: - model: Nexus6 version: 25 diff --git a/docs/syncIntegrationTests.md b/docs/syncIntegrationTests.md new file mode 100644 index 0000000000..b84bdfef4a --- /dev/null +++ b/docs/syncIntegrationTests.md @@ -0,0 +1,26 @@ +### Sync Integration Tests +The aim of these tests is to check that the synchronization is working between Fenix and Desktop. The intention is to add tests for History, Bookmarks, Tabs and Logins. +At this moment only tests for History and Bookmarks are defined. + +### Steps to Run +To run these tests you will need Python 2 and pipenv installed. Once you have these, make sure you're in the `syncintegration` directory and run the following: + +`$ pipenv install` +`$ pipenv run pytest` + +When a test is launched a stage account is created. That will be used both in Desktop and Fenix to be sure that what is saved in one place is shown in the other. + +The process for example for History item Desktop -> Fenix, would be: +- Desktop is launched, user signed in and history item created. +- Android sim is launched (Pixel 3 API28), Fenix app starts and same user is signed in, then we go to History list and verify that the item is there. + + +### Results +Due to the set up necessary these tests do not run as part of the regular CI, via Taskcluster. +The idea is to have them running on Jenkins periodically (TBD how often). +Once they finish there is a slack notificattion received informing about the result (so far that is configured for #firefox-ios-alerts) + +A html file is generated with all the info, for each step to make it easy to debug in case of failure. + +## Notes +More detailed info can be found [`here`](https://docs.google.com/document/d/1dhxlbGQBA6aJi2Xz-CsJZuGJPRReoL7nfm9cYu4HcZI/edit?usp=sharing)