diff --git a/.circleci/config.yml b/.circleci/config.yml index 1cccb581..e42f9564 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ references: persist_to_workspace: root: *workspace paths: + - mobile/build/intermediates - mobile/build/outputs/androidTest-results - mobile/build/outputs/apk - mobile/build/outputs/code-coverage @@ -162,6 +163,30 @@ jobs: path: mobile/build/outputs/mapping/ destination: /mapping/ + ## Check code quality + + check_quality: + <<: *android_config + steps: + - checkout + - *restore_cache + - run: + name: Download dependencies + command: ./gradlew androidDependencies + - *save_cache + - *export_gservices_key + - *decode_gservices_key + - run: + name: Run Checkstyle + command: ./gradlew -PciBuild=true :mobile:customCheckstyle + - run: + name: Run FindBugs + command: ./gradlew -PciBuild=true :mobile:customFindBugs + - *persist_debug_workspace + - store_artifacts: + path: mobile/build/reports/ + destination: /reports/ + ## Run unit tests test_unit: @@ -267,7 +292,12 @@ workflows: jobs: - build_debug - build_release - - test_unit + - check_quality: + requires: + - build_debug + - test_unit: + requires: + - build_debug - test_instrumented: requires: - build_debug @@ -282,5 +312,6 @@ workflows: - master requires: - build_release + - check_quality - test_unit - test_instrumented diff --git a/.gitignore b/.gitignore index 1199b826..70da2796 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,8 @@ Temporary Items # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +.idea/caches/** + # User-specific stuff: .idea/*.xml .idea/*/*.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 71ae4b71..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 4987ecab..00000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 2859541d..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d..00000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE index 11ade666..f332ede6 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. -   Copyright 2016-2017 Alan Tai +   Copyright 2016-2018 Alan Tai Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 4398f9c8..64c7838c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ Newspaper ========= -[![Release](https://img.shields.io/github/release/ayltai/Newspaper.svg?label=release&maxAge=1800)](https://1468-77390316-gh.circle-artifacts.com/0/apk/release/mobile-release.apk) [![Build Status](https://circleci.com/gh/ayltai/Newspaper.svg?style=shield)](https://circleci.com/gh/ayltai/Newspaper) [![Code Quality](https://api.codacy.com/project/badge/Grade/89d745ac9331474e9cf9f3203782a72f)](https://www.codacy.com/app/ayltai/Newspaper?utm_source=github.com&utm_medium=referral&utm_content=ayltai/Newspaper&utm_campaign=Badge_Grade) [![Code Coverage](https://api.codacy.com/project/badge/Coverage/89d745ac9331474e9cf9f3203782a72f)](https://www.codacy.com/app/ayltai/Newspaper?utm_source=github.com&utm_medium=referral&utm_content=ayltai/Newspaper&utm_campaign=Badge_Coverage) [![Code Coverage](https://codecov.io/gh/ayltai/Newspaper/branch/master/graph/badge.svg)](https://codecov.io/gh/ayltai/Newspaper) [![Android API](https://img.shields.io/badge/API-16%2B-blue.svg?style=flat&label=API&maxAge=300)](https://www.android.com/history/) [![License](https://img.shields.io/badge/License-apache%202.0-blue.svg?label=license&maxAge=1800)](https://github.com/ayltai/Newspaper/blob/master/LICENSE) +[![Release](https://img.shields.io/github/release/ayltai/Newspaper.svg?label=release&maxAge=1800)](https://1677-77390316-gh.circle-artifacts.com/0/apk/release/mobile-release.apk) [![Build Status](https://circleci.com/gh/ayltai/Newspaper.svg?style=shield)](https://circleci.com/gh/ayltai/Newspaper) [![Code Quality](https://api.codacy.com/project/badge/Grade/89d745ac9331474e9cf9f3203782a72f)](https://www.codacy.com/app/ayltai/Newspaper?utm_source=github.com&utm_medium=referral&utm_content=ayltai/Newspaper&utm_campaign=Badge_Grade) [![Code Coverage](https://api.codacy.com/project/badge/Coverage/89d745ac9331474e9cf9f3203782a72f)](https://www.codacy.com/app/ayltai/Newspaper?utm_source=github.com&utm_medium=referral&utm_content=ayltai/Newspaper&utm_campaign=Badge_Coverage) [![Code Coverage](https://codecov.io/gh/ayltai/Newspaper/branch/master/graph/badge.svg)](https://codecov.io/gh/ayltai/Newspaper) [![Android API](https://img.shields.io/badge/API-16%2B-blue.svg?style=flat&label=API&maxAge=300)](https://www.android.com/history/) [![License](https://img.shields.io/badge/License-apache%202.0-blue.svg?label=license&maxAge=1800)](https://github.com/ayltai/Newspaper/blob/master/LICENSE) An aggregated news app containing news from 10+ local news publishers in Hong Kong. Made with ❤ -![Screenshot (Compact)](design/screenshot_cozy_framed.png "Screenshot (Cozy)") ![Screenshot (Dark)](design/screenshot_dark_framed.png "Screenshot (Dark)") +![Screenshot (Compact)](design/screenshot_cozy_framed.png "Screenshot (Cozy)") ![Screenshot (Dark)](design/screenshot_dark_framed.png "Screenshot (Dark)") ![Screenshot (Details)](design/screenshot_details_framed.png "Screenshot (Details)") ## Features * Fast. Just fast. * Support video news * Auto face centering image cropping -* Optional panoramic images +* Analyze news contents using [Natural Language Processing](https://en.wikipedia.org/wiki/Natural-language_processing) * Read news from 10 local news publishers * Bookmarks and reading history * No ads. We hate ads as much as you do @@ -53,7 +53,6 @@ This app is made with the support of open source projects: * [BigImageViewer](https://github.com/Piasy/BigImageViewer) * [RecyclerView Animators](https://github.com/wasabeef/recyclerview-animators) * [KenBurnsView](https://github.com/flavioarfaria/KenBurnsView) -* [PanoramaImageView](https://github.com/gjiazhe/PanoramaImageView) * [ExoPlayer](https://github.com/google/ExoPlayer) * [ShimmerLayout](https://github.com/team-supercharge/ShimmerLayout) * [SmallBang](https://github.com/hanks-zyh/SmallBang) @@ -74,9 +73,8 @@ This app is made with the support of open source projects: * [Google Mobile Vision](https://developers.google.com/vision/face-detection-concepts) * [Firebase Analytics](https://firebase.google.com/docs/analytics) * [Firebase Remote Config](https://firebase.google.com/docs/remote-config) -* [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon) * [Firebase Test Lab](https://firebase.google.com/docs/test-lab) * [CircleCI](https://circleci.com) * [Fabric Answers](https://answers.io) * [Fabric Crashlytics](https://fabric.io/kits/android/crashlytics) -* [Instabug](https://instabug.com) +* [TextRazor](https://www.textrazor.com) diff --git a/build.gradle b/build.gradle index 25434a77..ba24e78b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,12 +6,12 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'io.realm:realm-gradle-plugin:4.3.4' - classpath 'com.google.gms:google-services:3.2.0' + classpath 'com.android.tools.build:gradle:3.1.2' + classpath 'io.realm:realm-gradle-plugin:5.1.0' + classpath 'com.google.gms:google-services:3.3.1' classpath 'com.google.firebase:firebase-plugins:1.1.5' - classpath 'io.fabric.tools:gradle:1.25.1' - classpath 'org.jacoco:org.jacoco.core:0.7.9' + classpath 'io.fabric.tools:gradle:1.25.4' + classpath 'org.jacoco:org.jacoco.core:0.8.1' } } diff --git a/checkstyle.xml b/checkstyle.xml index 0ba3ec47..5974eb28 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -356,7 +356,7 @@ + value="12"/> - + value="-1,0,0.5,1,2,10,100"/> - diff --git a/design/screenshot_cozy_framed.png b/design/screenshot_cozy_framed.png index 01212d9b..7f3b5e65 100644 Binary files a/design/screenshot_cozy_framed.png and b/design/screenshot_cozy_framed.png differ diff --git a/design/screenshot_dark_framed.png b/design/screenshot_dark_framed.png index 698dabd9..fe902cc6 100644 Binary files a/design/screenshot_dark_framed.png and b/design/screenshot_dark_framed.png differ diff --git a/design/screenshot_details_framed.png b/design/screenshot_details_framed.png new file mode 100644 index 00000000..22cf0bc2 Binary files /dev/null and b/design/screenshot_details_framed.png differ diff --git a/findbugs-android-exclude.xml b/findbugs-android-exclude.xml index 0803af11..5c5f5f87 100644 --- a/findbugs-android-exclude.xml +++ b/findbugs-android-exclude.xml @@ -1,13 +1,33 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index f83aba8e..c55f6db6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,4 @@ org.gradle.jvmargs=-noverify -Xmx1536m org.gradle.caching=true -org.gradle.configureondemand=true org.gradle.parallel=false org.gradle.daemon=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1c1b1ae3..4de66b97 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip diff --git a/mobile/build.gradle b/mobile/build.gradle index 528233f1..8b25bb43 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'com.google.firebase.firebase-perf' apply plugin: 'io.fabric' apply plugin: 'realm-android' apply plugin: 'checkstyle' @@ -7,9 +6,19 @@ apply plugin: 'findbugs' apply plugin: 'jacoco' jacoco { - toolVersion '0.7.9' + toolVersion '0.8.1' } +checkstyle { + toolVersion '8.10' +} + +findbugs { + toolVersion '3.0.1' +} + +String NLP_API_TOKEN = System.getenv('NLP_API_TOKEN') + String VERSION_REVISION = 'git rev-list --count HEAD'.execute().text.trim() String VERSION_HASH = 'git rev-parse --short HEAD'.execute().text.trim() @@ -20,8 +29,8 @@ android { applicationId 'com.github.ayltai.newspaper' minSdkVersion project.hasProperty('ciBuild') ? 16 : 21 targetSdkVersion 27 - versionCode 28 - versionName '3.10.' + VERSION_REVISION + '-' + VERSION_HASH + versionCode 31 + versionName '4.2.' + VERSION_REVISION + '-' + VERSION_HASH testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' testInstrumentationRunnerArguments disableAnalytics : 'true' @@ -29,8 +38,6 @@ android { vectorDrawables { useSupportLibrary true } - - resConfigs 'auto' } signingConfigs { @@ -48,6 +55,8 @@ android { testCoverageEnabled true ext.enableCrashlytics = false + + buildConfigField 'String', 'NLP_API_TOKEN', '\"' + NLP_API_TOKEN + '\"' } release { @@ -55,6 +64,8 @@ android { shrinkResources true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + buildConfigField 'String', 'NLP_API_TOKEN', '\"' + NLP_API_TOKEN + '\"' } } @@ -70,8 +81,7 @@ android { lintOptions { abortOnError false - disable 'RestrictedApi' - disable 'MissingTranslation' + disable 'MissingTranslation', 'RestrictedApi' } aaptOptions { @@ -98,19 +108,20 @@ configurations { } ext { - multidexVersion = '1.0.2' - supportLibraryVersion = '27.0.2' - architectureVersion = '1.1.0' - firebaseSdkVersion = '11.8.0' - daggerVersion = '2.14.1' - retrofitVersion = '2.3.0' - frescoVersion = '1.8.1' + multidexVersion = '1.0.3' + supportLibraryVersion = '27.1.1' + architectureVersion = '1.1.1' + firebaseSdkVersion = '15.0.2' + daggerVersion = '2.16' + retrofitVersion = '2.4.0' + frescoVersion = '1.9.0' bigImageViewerVersion = '1.4.6' - exoPlayerVersion = '2.7.0' + exoPlayerVersion = '2.8.0' + autoValueVersion = '1.6' leakCanaryVersion = '1.5.4' - robolectricVersion = '3.7.1' powerMockVersion = '2.0.0-beta.5' - espressoVersion = '3.0.1' + robolectricVersion = '3.8' + espressoVersion = '3.0.2' } repositories { @@ -120,7 +131,7 @@ repositories { } dependencies { - implementation fileTree(dir : 'libs', include : [ '*.jar' ]) + androidTestImplementation fileTree(dir : 'libs', include : [ '*.jar' ]) // Multi-dex support debugImplementation "com.android.support:multidex:$multidexVersion" @@ -143,11 +154,10 @@ dependencies { // Firebase implementation "com.google.firebase:firebase-core:$firebaseSdkVersion" implementation "com.google.firebase:firebase-config:$firebaseSdkVersion" - implementation "com.google.firebase:firebase-perf:$firebaseSdkVersion" // Reactive programming - implementation 'io.reactivex.rxjava2:rxjava:2.1.10' - implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' + implementation 'io.reactivex.rxjava2:rxjava:2.1.13' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' // Dependency injection implementation "com.google.dagger:dagger:$daggerVersion" @@ -164,7 +174,7 @@ dependencies { } // Database - implementation 'io.realm:android-adapters:2.1.1' + implementation 'io.realm:android-adapters:3.0.0' // Image processing implementation "com.facebook.fresco:fresco:$frescoVersion" @@ -179,33 +189,32 @@ dependencies { implementation "com.google.android.exoplayer:exoplayer-ui:$exoPlayerVersion" implementation "com.google.android.exoplayer:extension-okhttp:$exoPlayerVersion" + // Natural language processing + implementation 'com.textrazor:textrazor:1.0.11' + // UI components implementation 'com.squareup.flow:flow:1.0.0-alpha3' - implementation 'com.nex3z:flow-layout:1.1.0' + implementation 'com.nex3z:flow-layout:1.2.2' // Eye candies implementation 'jp.wasabeef:recyclerview-animators:2.3.0' - implementation 'io.supercharge:shimmerlayout:2.0.0' + implementation 'io.supercharge:shimmerlayout:2.1.0' implementation 'com.github.castorflex.smoothprogressbar:library:1.3.0' implementation 'pub.hanks:smallbang:1.2.2' implementation 'com.flaviofaria:kenburnsview:1.0.7' - implementation 'com.gjiazhe:PanoramaImageView:1.0' implementation 'io.github.inflationx:calligraphy3:3.0.0' // Code generation tools - compileOnly "com.jakewharton.auto.value:auto-value-annotations:1.5" - annotationProcessor "com.google.auto.value:auto-value:1.5.3" + compileOnly "com.google.auto.value:auto-value-annotations:$autoValueVersion" + annotationProcessor "com.google.auto.value:auto-value:$autoValueVersion" annotationProcessor 'com.ryanharter.auto.value:auto-value-parcel:0.2.6' - implementation 'com.google.code.gson:gson:2.8.2' + implementation 'com.google.code.gson:gson:2.8.4' // Fabric - implementation ('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { + implementation ('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { transitive = true } - // Instabug - implementation 'com.instabug.library:instabug:4.10.2' - // Debugging implementation 'com.akaita.java:rxjava2-debug:1.2.2' debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" @@ -213,7 +222,8 @@ dependencies { testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" // Unit testing - testImplementation 'org.mockito:mockito-core:2.15.3' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.18.6' testImplementation "org.powermock:powermock-module-junit4:$powerMockVersion" testImplementation "org.powermock:powermock-module-junit4-rule:$powerMockVersion" testImplementation "org.powermock:powermock-api-mockito2:$powerMockVersion" @@ -233,10 +243,10 @@ dependencies { androidTestImplementation ("com.android.support.test.espresso:espresso-intents:$espressoVersion") { exclude group : 'com.android.support' } - androidTestUtil 'com.android.support.test:orchestrator:1.0.1' + androidTestUtil 'com.android.support.test:orchestrator:1.0.2' // Code coverage by Codacy - codacy 'com.github.codacy:codacy-coverage-reporter:2.0.2' + codacy 'com.github.codacy:codacy-coverage-reporter:4.0.0' } configurations.all { @@ -245,29 +255,53 @@ configurations.all { } } -task customFindBugs(type : FindBugs) { - ignoreFailures = false - effort = "max" - reportLevel = "medium" - classes = files("$project.buildDir/intermediates/classes") +task customCheckstyle(type : Checkstyle) { + description = 'Running Checkstyle' + group = 'verification' + ignoreFailures = true + showViolations = true - // Use this only if you want exclude some errors - excludeFilter = file("$rootProject.rootDir/findbugs-android-exclude.xml") + configFile file("$rootProject.rootDir/checkstyle.xml") + + source 'src/main/java', 'src/debug/java' + include '**/*.java' + exclude '**/*Test.java' + exclude '**/gen/**' + exclude '**/R.java' + exclude '**/BuildConfig.java' + + reports { + xml.enabled = false + html.enabled = true + xml.destination file("$project.buildDir/reports/checkstyle/checkstyle-output.xml") + html.destination file("$project.buildDir/reports/checkstyle/checkstyle-output.html") + } - source = fileTree('src/main/java/') classpath = files() +} + +task customFindBugs(type : FindBugs, dependsOn : 'assembleDebug') { + description = 'Runing FindBugs' + group = 'verification' + effort = 'max' + reportLevel = 'medium' + source = fileTree('src/main/java') + classes = fileTree('build/intermediates/classes/debug') + classpath = files() + ignoreFailures = true + + // Use this only if you want exclude some errors + excludeFilter = file("$rootProject.rootDir/findbugs-android-exclude.xml") reports { xml.enabled = false xml.withMessages = true html.enabled = true - xml.destination file("$project.buildDir/outputs/findbugs/findbugs-output.xml") - html.destination file("$project.buildDir/outputs/findbugs/findbugs-output.html") + xml.destination file("$project.buildDir/reports/findbugs/findbugs-output.xml") + html.destination file("$project.buildDir/reports/findbugs/findbugs-output.html") } } -assemble.dependsOn customFindBugs - def coverageSourceDirs = [ 'src/main/java', 'src/debug/java' @@ -278,8 +312,8 @@ tasks.withType(Test) { } task jacocoTestReport(type : JacocoReport, dependsOn : 'testDebugUnitTest') { - group = 'Reporting' description = 'Generate JaCoCo coverage reports' + group = 'Reporting' reports { xml.enabled = true @@ -327,6 +361,7 @@ task uploadCoverageToCodacy(type : JavaExec, dependsOn : jacocoTestReport) { classpath = configurations.codacy args = [ + "report", "-l", "Java", "-r", diff --git a/mobile/proguard-rules.pro b/mobile/proguard-rules.pro index d672783e..649f5c0c 100644 --- a/mobile/proguard-rules.pro +++ b/mobile/proguard-rules.pro @@ -1,6 +1,10 @@ -keepattributes *Annotation*,EnclosingMethod,Signature -keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet, int); + public (android.content.Context, android.util.AttributeSet, int); +} +-keepclassmembernames enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); } -keepnames class * implements android.os.Parcelable { public static final ** CREATOR; @@ -37,9 +41,6 @@ ## Retrofit -dontwarn retrofit2.** -## SearchView --keep class android.support.v7.widget.SearchView { *; } - ## SimpleXML -dontwarn org.simpleframework.xml.stream.** -keep class org.simpleframework.xml.** { *; } @@ -48,10 +49,20 @@ public (); } +## TextRazor +-keep class com.textrazor.** { *; } +-keepnames class com.fasterxml.jackson.** { *; } +-keepnames interface com.fasterxml.jackson.** { *; } +-dontwarn com.fasterxml.jackson.databind.** +-keep class org.codehaus.** { *; } +-keepclassmembernames public final enum org.codehaus.jackson.annotate.JsonAutoDetect$Visibility { + public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; +} + +## SearchView +-keep class android.support.v7.widget.SearchView { *; } + ## BottomNavigationView -keepclassmembers class android.support.design.internal.BottomNavigationMenuView { boolean mShiftingMode; } - -## Instabug --dontwarn com.instabug.** diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/AboutTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/AboutTest.java index 58c32e46..da4e0d0c 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/AboutTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/AboutTest.java @@ -10,13 +10,13 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - @SmallTest @RunWith(AndroidJUnit4.class) public final class AboutTest extends BaseTest { @@ -25,8 +25,7 @@ public void visitWebsite() { // Clicks About bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_about), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".visitWebsite", this.testRule.getActivity()); @@ -38,7 +37,7 @@ public void visitWebsite() { // Clicks Visit button Espresso.onView(ViewMatchers.withId(R.id.visit_container)) - .perform(ViewActions.click()); + .perform(ViewActions.scrollTo(), ViewActions.click()); // Checks that the fired Intent is correct Intents.intended(intent); @@ -50,8 +49,7 @@ public void rateApp() { // Clicks About bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_about), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".rateApp", this.testRule.getActivity()); @@ -63,7 +61,7 @@ public void rateApp() { // Clicks Rate button Espresso.onView(ViewMatchers.withId(R.id.rate_container)) - .perform(ViewActions.click()); + .perform(ViewActions.scrollTo(), ViewActions.click()); // Checks that the fired Intent is correct Intents.intended(intent); diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/BaseTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/BaseTest.java index 069c1138..255eb23d 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/BaseTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/BaseTest.java @@ -1,16 +1,16 @@ package com.github.ayltai.newspaper; -import java.io.File; - import android.support.annotation.CallSuper; import android.support.test.rule.ActivityTestRule; +import com.github.ayltai.newspaper.app.MainActivity; +import com.github.ayltai.newspaper.util.MoreTestUtils; + import org.junit.After; import org.junit.Before; import org.junit.Rule; -import com.github.ayltai.newspaper.app.MainActivity; -import com.github.ayltai.newspaper.util.MoreTestUtils; +import java.io.File; public abstract class BaseTest { @Rule diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/BookmarkedNewsTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/BookmarkedNewsTest.java index 789361c0..0ad0e782 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/BookmarkedNewsTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/BookmarkedNewsTest.java @@ -7,14 +7,13 @@ import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @LargeTest @RunWith(AndroidJUnit4.class) public final class BookmarkedNewsTest extends BaseTest { @@ -23,16 +22,13 @@ public void bookmarkNewsTest() { // Clicks Bookmarks bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_bookmark), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -48,15 +44,13 @@ public void bookmarkNewsTest() { // Checks that the empty placeholder is displayed Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.empty_title), - ViewMatchers.withText("You don't have any favorite news yet"), - ViewMatchers.isDisplayed())) + ViewMatchers.withText("You don't have any favorite news yet"))) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); // Clicks News bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_news), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_LONG); @@ -68,13 +62,12 @@ public void bookmarkNewsTest() { ViewMatchers.isDisplayed())) .perform(ViewActions.click()); - MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); + MoreTestUtils.sleep(MoreTestUtils.DURATION_LONG); // Clicks Bookmark button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_bookmark), - ViewMatchers.withContentDescription("Bookmark"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Bookmark"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -85,8 +78,7 @@ public void bookmarkNewsTest() { // Clicks Bookmarks bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_bookmark), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -96,15 +88,13 @@ public void bookmarkNewsTest() { // Checks that there is a bookmarked news item Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3), - ViewMatchers.isDisplayed())) + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3))) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); // Clicks Search button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_search), - ViewMatchers.withContentDescription("Search"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Search"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -112,9 +102,7 @@ public void bookmarkNewsTest() { ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".search", this.testRule.getActivity()); // Types a query into the search text box - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_src_text), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_src_text)) .perform( ViewActions.replaceText("asdfqwerzxcv"), ViewActions.closeSoftKeyboard()); @@ -126,14 +114,11 @@ public void bookmarkNewsTest() { // Checks that no search results returned Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3), - ViewMatchers.isDisplayed())) + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3))) .check(ViewAssertions.doesNotExist()); // Clears the search query - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_close_btn), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_close_btn)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -143,8 +128,7 @@ public void bookmarkNewsTest() { // Collapses the search text box Espresso.onView(Matchers.allOf( ViewMatchers.withContentDescription("Collapse"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -154,8 +138,7 @@ public void bookmarkNewsTest() { // Checks that there is a bookmarked news item Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3), - ViewMatchers.isDisplayed())) + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3))) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); } } diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/DetailsTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/DetailsTest.java index 5ec2bdf6..ef637875 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/DetailsTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/DetailsTest.java @@ -12,13 +12,13 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; + import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.github.ayltai.newspaper.util.MoreTestUtils; - @SmallTest @RunWith(AndroidJUnit4.class) public final class DetailsTest extends BaseTest { @@ -29,41 +29,35 @@ public void detailsTest() { ViewMatchers.withId(R.id.recyclerView)))) .perform(RecyclerViewActions.actionOnItemAtPosition(2, ViewActions.click())); - MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); + MoreTestUtils.sleep(MoreTestUtils.DURATION_LONG); - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.avatar), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + Espresso.onView(ViewMatchers.withId(R.id.avatar)) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.source), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + Espresso.onView(ViewMatchers.withId(R.id.source)) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_bookmark), - ViewMatchers.withContentDescription("Bookmark"), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + ViewMatchers.withContentDescription("Bookmark"))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_share), - ViewMatchers.withContentDescription("Share"), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + ViewMatchers.withContentDescription("Share"))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.title), ViewMatchers.withParent(Matchers.allOf( ViewMatchers.withClassName(Matchers.is("android.widget.LinearLayout")), ViewMatchers.withParent(ViewMatchers.withId(R.id.container)) - )), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + )))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.description), + ViewMatchers.withParent(ViewMatchers.withClassName(Matchers.is("android.widget.LinearLayout"))), ViewMatchers.isDisplayed())) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); @@ -77,8 +71,7 @@ public void detailsTest() { // Clicks Share button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_share), - ViewMatchers.withContentDescription("Share"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Share"))) .perform(ViewActions.click()); // Checks that the fired Intent is correct @@ -88,8 +81,7 @@ public void detailsTest() { // Clicks Text-to-Speech button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_text_to_speech), - ViewMatchers.withContentDescription("Text to speech"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Text to speech"))) .perform(ViewActions.click()); } } diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/FeaturedItemTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/FeaturedItemTest.java index d34eb779..aa8e8bf8 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/FeaturedItemTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/FeaturedItemTest.java @@ -7,14 +7,13 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @SmallTest @RunWith(AndroidJUnit4.class) public final class FeaturedItemTest extends BaseTest { @@ -41,8 +40,7 @@ public void featuredItemTest() { // Checks that the news image is displayed within the toolbar Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.image_container), 0), 0), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.image_container), 0), 0))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); } } diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/GoToTopTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/GoToTopTest.java index 231de681..9c1c7298 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/GoToTopTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/GoToTopTest.java @@ -7,14 +7,13 @@ import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @MediumTest @RunWith(AndroidJUnit4.class) public class GoToTopTest extends BaseTest { @@ -53,17 +52,13 @@ public void goToTopTest() { .check(ViewAssertions.doesNotExist()); // Clicks the More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks the Up button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_up), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_up)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/HistoricalNewsTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/HistoricalNewsTest.java index 9434d716..b644ee3f 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/HistoricalNewsTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/HistoricalNewsTest.java @@ -7,14 +7,13 @@ import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @LargeTest @RunWith(AndroidJUnit4.class) public final class HistoricalNewsTest extends BaseTest { @@ -23,16 +22,13 @@ public void historicalNewsTest() { // Clicks Histories bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_history), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -48,15 +44,13 @@ public void historicalNewsTest() { // Checks that the empty placeholder is displayed Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.empty_title), - ViewMatchers.withText("You haven't read any news yet"), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + ViewMatchers.withText("You haven't read any news yet"))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Clicks News bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_news), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_LONG); @@ -68,7 +62,7 @@ public void historicalNewsTest() { ViewMatchers.isDisplayed())) .perform(ViewActions.click()); - MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); + MoreTestUtils.sleep(MoreTestUtils.DURATION_LONG); // Goes back the News list Espresso.pressBack(); @@ -76,8 +70,7 @@ public void historicalNewsTest() { // Clicks Histories bottom tab Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_history), - ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(R.id.bottomNavigationView))))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -87,15 +80,13 @@ public void historicalNewsTest() { // Checks that there is a historical news item Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Clicks Search button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_search), - ViewMatchers.withContentDescription("Search"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Search"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -103,9 +94,7 @@ public void historicalNewsTest() { ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".search", this.testRule.getActivity()); // Types a query into the search text box - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_src_text), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_src_text)) .perform( ViewActions.replaceText("asdfqwerzxcv"), ViewActions.closeSoftKeyboard()); @@ -117,14 +106,11 @@ public void historicalNewsTest() { // Checks that no search results returned Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3), - ViewMatchers.isDisplayed())) + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3))) .check(ViewAssertions.doesNotExist()); // Clears the search query - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_close_btn), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_close_btn)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -134,8 +120,7 @@ public void historicalNewsTest() { // Collapses the search text box Espresso.onView(Matchers.allOf( ViewMatchers.withContentDescription("Collapse"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -145,8 +130,7 @@ public void historicalNewsTest() { // Checks that there is a bookmarked news item Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.image), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.container), 0), 3))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); } } diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/MainTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/MainTest.java index c99f5e82..2f1f06da 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/MainTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/MainTest.java @@ -7,12 +7,12 @@ import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.github.ayltai.newspaper.util.MoreTestUtils; - @MediumTest @RunWith(AndroidJUnit4.class) public final class MainTest extends BaseTest { diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/SearchTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/SearchTest.java index 80ed1708..e5afbaa2 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/SearchTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/SearchTest.java @@ -8,15 +8,14 @@ import android.support.test.runner.AndroidJUnit4; import android.view.View; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.hamcrest.core.IsInstanceOf; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @MediumTest @RunWith(AndroidJUnit4.class) public final class SearchTest extends BaseTest { @@ -31,8 +30,7 @@ public void searchWithResultsTest() { // Clicks Search button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_search), - ViewMatchers.withContentDescription("Search"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Search"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -40,19 +38,15 @@ public void searchWithResultsTest() { ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".search", this.testRule.getActivity()); // Checks that the search text box is displayed - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_src_text), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + Espresso.onView(ViewMatchers.withId(R.id.search_src_text)) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".search.textBox", this.testRule.getActivity()); // Types a query into the search text box - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_src_text), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_src_text)) .perform( ViewActions.replaceText("香港"), ViewActions.closeSoftKeyboard()); @@ -68,9 +62,7 @@ public void searchWithResultsTest() { .check(ViewAssertions.doesNotExist()); // Clears the search query - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_close_btn), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_close_btn)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -80,8 +72,7 @@ public void searchWithResultsTest() { // Collapses the search text box Espresso.onView(Matchers.allOf( ViewMatchers.withContentDescription("Collapse"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -106,8 +97,7 @@ public void searchWithoutResultsTest() { // Clicks Search button Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_search), - ViewMatchers.withContentDescription("Search"), - ViewMatchers.isDisplayed())) + ViewMatchers.withContentDescription("Search"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -118,16 +108,14 @@ public void searchWithoutResultsTest() { Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.search_src_text), ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".search.textBox", this.testRule.getActivity()); // Types a query into the search text box - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_src_text), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_src_text)) .perform( ViewActions.replaceText("asdfqwerzxcv"), ViewActions.closeSoftKeyboard()); @@ -143,14 +131,11 @@ public void searchWithoutResultsTest() { ViewMatchers.withId(R.id.image), MoreTestUtils.childAtPosition(Matchers.allOf( ViewMatchers.withId(R.id.container), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(IsInstanceOf.instanceOf(android.widget.FrameLayout.class), 0), 0)), 0)), 0), - ViewMatchers.isDisplayed())) + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(IsInstanceOf.instanceOf(android.widget.FrameLayout.class), 0), 0)), 0)), 0))) .check(ViewAssertions.doesNotExist()); // Clears the search query - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.search_close_btn), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.search_close_btn)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -160,8 +145,7 @@ public void searchWithoutResultsTest() { // Collapses the search text box Espresso.onView(Matchers.allOf( ViewMatchers.withContentDescription("Collapse"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/SettingsDialogTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/SettingsDialogTest.java index e8bc42b5..71951ef7 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/SettingsDialogTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/SettingsDialogTest.java @@ -7,23 +7,20 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @SmallTest @RunWith(AndroidJUnit4.class) public final class SettingsDialogTest extends BaseTest { @Test public void settingsDialogTest() { // Clicks More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -39,15 +36,11 @@ public void settingsDialogTest() { ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".settingsDialog", this.testRule.getActivity()); // Checks that Apply button is displayed - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_ok), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + Espresso.onView(ViewMatchers.withId(R.id.action_ok)) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Clicks Categories tab - Espresso.onView(Matchers.allOf( - ViewMatchers.withText("Categories"), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withText("Categories")) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -57,39 +50,31 @@ public void settingsDialogTest() { // De-selects a category Espresso.onView(Matchers.allOf( ViewMatchers.withText("兩岸"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.flowLayout)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.flowLayout)))) .perform(ViewActions.click()); // Clicks Apply Changes Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_ok), - ViewMatchers.withText("Apply changes"), - ViewMatchers.isDisplayed())) + ViewMatchers.withText("Apply changes"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_MEDIUM); // Clicks More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks Settings button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_settings), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_settings)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks Categories tab - Espresso.onView(Matchers.allOf( - ViewMatchers.withText("Categories"), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withText("Categories")) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -99,8 +84,7 @@ public void settingsDialogTest() { // Checks if the specific category is de-selected Espresso.onView(Matchers.allOf( ViewMatchers.withText("兩岸"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.flowLayout)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.flowLayout)))) .check(ViewAssertions.matches(MoreTestUtils.isNotSelected())); } } diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/VideoActivityTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/VideoActivityTest.java index 3daf8c87..bd62dc09 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/VideoActivityTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/VideoActivityTest.java @@ -1,7 +1,5 @@ package com.github.ayltai.newspaper; -import java.io.File; - import android.content.Intent; import android.support.annotation.CallSuper; import android.support.test.InstrumentationRegistry; @@ -12,14 +10,16 @@ import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.app.VideoActivity; +import com.github.ayltai.newspaper.util.MoreTestUtils; + import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import com.github.ayltai.newspaper.app.VideoActivity; -import com.github.ayltai.newspaper.util.MoreTestUtils; +import java.io.File; @SmallTest @RunWith(AndroidJUnit4.class) @@ -42,11 +42,10 @@ public void setUp() { } @Test - public void test() { + public void videoActivityTest() { Espresso.onView(Matchers.allOf( - ViewMatchers.withClassName(Matchers.is("com.google.android.exoplayer2.ui.SimpleExoPlayerView")), - ViewMatchers.withId(R.id.video), - ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + ViewMatchers.withClassName(Matchers.is("com.google.android.exoplayer2.ui.PlayerView")), + ViewMatchers.withId(R.id.video))) + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); } } diff --git a/mobile/src/androidTest/java/com/github/ayltai/newspaper/ViewStyleTest.java b/mobile/src/androidTest/java/com/github/ayltai/newspaper/ViewStyleTest.java index f3474b2c..6de37b94 100644 --- a/mobile/src/androidTest/java/com/github/ayltai/newspaper/ViewStyleTest.java +++ b/mobile/src/androidTest/java/com/github/ayltai/newspaper/ViewStyleTest.java @@ -7,39 +7,32 @@ import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import com.github.ayltai.newspaper.util.MoreTestUtils; +import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; -import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter; - -import com.github.ayltai.newspaper.util.MoreTestUtils; - @MediumTest @RunWith(AndroidJUnit4.class) public final class ViewStyleTest extends BaseTest { @Test public void viewStyleTest() { // Clicks More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks Settings button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_settings), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_settings)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks Settings tab - Espresso.onView(Matchers.allOf( - ViewMatchers.withText("Settings"), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withText("Settings")) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -47,53 +40,43 @@ public void viewStyleTest() { ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".settings.cozy", this.testRule.getActivity()); // Checks that Cozy view style is checked - Espresso.onView(Matchers.allOf( - MoreTestUtils.childAtPosition(Matchers.allOf( - ViewMatchers.withId(R.id.linearLayout), - MoreTestUtils.childAtPosition(ViewMatchers.withParent(Matchers.allOf( - ViewMatchers.withId(R.id.viewPager), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(Matchers.allOf( - ViewMatchers.withId(R.id.design_bottom_sheet), - MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.coordinator), 1)), 0), 0), 1))), 0)), 0), - ViewMatchers.isDisplayed())) + Espresso.onView(MoreTestUtils.childAtPosition(Matchers.allOf( + ViewMatchers.withId(R.id.linearLayout), + MoreTestUtils.childAtPosition(ViewMatchers.withParent(Matchers.allOf( + ViewMatchers.withId(R.id.viewPager), + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(Matchers.allOf( + ViewMatchers.withId(R.id.design_bottom_sheet), + MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.coordinator), 1)), 0), 0), 1))), 0)), 0)) .check(ViewAssertions.matches(MoreTestUtils.isChecked())); // De-selects Cozy view style Espresso.onView(Matchers.allOf( ViewMatchers.withText("Cozy layout"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.linearLayout)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.linearLayout)))) .perform(ViewActions.click()); // Clicks Apply Changes Espresso.onView(Matchers.allOf( ViewMatchers.withId(R.id.action_ok), - ViewMatchers.withText("Apply changes"), - ViewMatchers.isDisplayed())) + ViewMatchers.withText("Apply changes"))) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_MEDIUM); // Clicks More button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_more), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_more)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks Settings button - Espresso.onView(Matchers.allOf( - ViewMatchers.withId(R.id.action_settings), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withId(R.id.action_settings)) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); // Clicks Settings tab - Espresso.onView(Matchers.allOf( - ViewMatchers.withText("Settings"), - ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withText("Settings")) .perform(ViewActions.click()); MoreTestUtils.sleep(MoreTestUtils.DURATION_SHORT); @@ -101,22 +84,19 @@ public void viewStyleTest() { ScreenShotter.takeScreenshot(this.getClass().getSimpleName() + ".settings.compact", this.testRule.getActivity()); // Checks that Cozy view style is unchecked - Espresso.onView(Matchers.allOf( - MoreTestUtils.childAtPosition(Matchers.allOf( - ViewMatchers.withId(R.id.linearLayout), - MoreTestUtils.childAtPosition(ViewMatchers.withParent(Matchers.allOf( - ViewMatchers.withId(R.id.viewPager), - MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(Matchers.allOf( - ViewMatchers.withId(R.id.design_bottom_sheet), - MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.coordinator), 1)), 0), 0), 1))), 0)), 0), - ViewMatchers.isDisplayed())) + Espresso.onView(MoreTestUtils.childAtPosition(Matchers.allOf( + ViewMatchers.withId(R.id.linearLayout), + MoreTestUtils.childAtPosition(ViewMatchers.withParent(Matchers.allOf( + ViewMatchers.withId(R.id.viewPager), + MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(MoreTestUtils.childAtPosition(Matchers.allOf( + ViewMatchers.withId(R.id.design_bottom_sheet), + MoreTestUtils.childAtPosition(ViewMatchers.withId(R.id.coordinator), 1)), 0), 0), 1))), 0)), 0)) .check(ViewAssertions.matches(MoreTestUtils.isNotChecked())); // Restores to Cozy view style Espresso.onView(Matchers.allOf( ViewMatchers.withText("Cozy layout"), - ViewMatchers.withParent(ViewMatchers.withId(R.id.linearLayout)), - ViewMatchers.isDisplayed())) + ViewMatchers.withParent(ViewMatchers.withId(R.id.linearLayout)))) .perform(ViewActions.click()); } } diff --git a/mobile/src/debug/assets/headline_realtime.html b/mobile/src/debug/assets/headline_realtime.html index 9b78f670..63e197ec 100644 --- a/mobile/src/debug/assets/headline_realtime.html +++ b/mobile/src/debug/assets/headline_realtime.html @@ -32,7 +32,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -58,7 +58,7 @@ - + @@ -69,8 +69,15 @@
- - + +
@@ -255,7 +259,7 @@

即時新聞港聞

  • 港聞
  • 財經
  • 地產
  • -
  • 娛樂
  • +
  • 娛樂
  • 中國
  • 國際
  • 體育
  • @@ -265,131 +269,131 @@

    即時新聞港聞

    - -

    將軍澳新都城第一期停車場冷氣機房發生爆炸,2名工人受傷,其中1人身體6成皮膚被燒傷,送院治理。消息指,有人疑在冷氣機房內點煙肇禍,消防正調查爆炸原因。事發在中午12時許,2名工人在冷氣機房清洗冷氣機,期間房內突然發生爆炸,2人走避不及,慘遭燒傷。消防接報趕至,迅速將火救熄。其中一名19歲工人6成皮膚被燒傷,由救護車送往...

    + +

    大埔廣福道爆水管!大量黃泥水由地底高壓噴出,足足有10層樓高。由於現場靠近鷺鳥林位置旁,有現場人士拍攝到照片顯示,有雀鳥懷疑受驚飛走。現場亦靠近民居及商舖,附近亦有老人院及投注站,距離不足十多米,大牆被噴濕,居民報警求助未知有否造成損失。事發於下午3時左右,在廣福道進入大埔墟方向,懷疑有地下水管爆裂,路面的路牌及指示牌...

    - 詳細 + 詳細

    - -

    教育大學民主牆凌晨有人張貼針對劉曉波夫婦的字句,標語早上被撕走。教大學生會不滿校方擅自拆除民主牆上的內容,認為是不尊重學生會。學生會並引述校方高層指,發生冒犯教育局副局長蔡若蓮事件後,有學校表明永不錄用教大的畢業生。教大學生會會長黎曉晴下午表示,民主牆出現針對劉曉波夫婦字句後,校方清晨透過短訊通知,但由於未能聯絡上,校...

    + +

    今日睇咗未?●Kelly Online:「最憎同人面對面坐」 兩巴士乘客「飛象過河」惹眾怒(按這裏閱讀)●心痛卓林遭女友擺佈 吳綺莉斥「利用別人名字找飯吃」(按這裏閱讀)●【有片】四川不肖女當街掌摑母親 正義路人一腳踢倒(按這裏閱讀)●大埔爆水管噴出10層樓「噴泉」夜鷺BB遭水柱擊中墮地傷(按這裏閱讀)●市民自言五音不...

    - 詳細 + 詳細

    - -

    天文台在下午12時15分發出之雷暴警告,有效時間延長至下午5時15分,預料香港有局部地區狂風雷暴。之前,天文台在上午9時25分曾發出雷暴警告,有效時間至上午10時30分,當時預料香港有局部地區雷暴。建立時間:09:30更新時間:15:35

    + +

    穿熱褲長髮夜歸女,凌晨途經旺角街頭,遭獨行賊從後推跌,女子手指受傷,內有衣物的環保袋被搶走;警方趕至在附近拘獲疑人,並在附近尋回被搶財物,女事主拒絕送院,在場助查時疑受驚過度,哭得梨花帶雨,須警員安慰。女事主姓陳,25歲。凌晨近5時,穿熱褲及蓄長髮的陳女,途經旺角花園街163號對開,突遭一名匪徒從後推跌,賊人趁她倒地時...

    - 詳細 + 詳細

    - -

    高山劇場新翼新建成的粵劇教育及資訊中心將於明日開放,中心內設6個區域,結合科技介紹清末民初至90年代現今的粵劇文化及特色,中心內亦集合80位粵劇名伶的資訊,及獨家播放24位名伶的訪問片段。6個區域分別為「歷史尋跡區」、「名伶區」、「劇本、唱腔及音樂區」、「虛擬舞台區」、「粵劇化妝區」及「閱聽區」。「歷史尋跡區」設有5部...

    + +

    一輛交由車房維修的值逾200萬元法拉利跑車,凌晨停泊深水埗街頭時,一輛私家車駛經後跑車突起火,消防員迅速將火撲熄,惟車頭冚燒黑慘遭「毀容」,警方相信跑車疑遭縱火,正追緝涉案私家車;代客維修跑車的車房職員無奈說:「不知如何向客人交代!」現場為福華街49號對開,一輛360 Spider F1型號的白色法拉利跑車停泊路邊,凌...

    - 詳細 + 詳細

    - -

    海關昨日在上水古洞檢獲約1.5公斤懷疑可卡因及懷疑冰毒,估計市值共約100萬元。海關人員昨午在古洞進行緝毒行動,在一鐵皮屋內檢獲這批懷疑毒品。其後,海關人員在上水拘捕兩名分別32及33歲懷疑涉案男子。案件仍在調查中。

    + +

    一年一度的荷蘭四月美食節,Orange Fest於星期五(27日)至星期日(29日)在科學園舉行,現場場面十分熱鬧,備有多個攤位售賣各種荷蘭美食,有大受歡迎的荷蘭鯡魚、青口、生蠔、荷蘭炸肉丸、炸肉捲及一流的鬆脆龍蝦包、安格斯牛肉漢堡和一系列串燒燒烤美食,真係數唔晒! 吃飽飽後又可以去工藝攤位展身手,現場有荷蘭木屐 Pa...

    - 詳細 + 詳細

    - -

    天文台調高今年進入香港500公里範圍的熱帶氣旋預測數字至6到9個後,天文台台長岑智明早上表示,下周有機會形成一至兩個熱帶氣旋,但暫未能估計會否吹襲香港。天文台表示,現時位於關島附近的低壓區可能會在未來兩三天逐漸發展,並為菲律賓以東海域帶來不穩定天氣。另外,日本氣象廳於今晨發布烈風警報,預料關島以東的熱帶低氣壓,最快將會...

    + +

    61歲姓溫男子,昨晚11時許駕駛私家車,沿大坑銅鑼灣道向灣仔行駛,當途經浣紗街交界時,突失控撞向一輛停在路邊煤氣掘路地盤的挖泥車,溫手部受傷,經治理後無礙,拒絕送院,惟未能通過酒精呼氣測試,涉嫌酒後駕駛被捕,帶署扣查。

    - 詳細 + 詳細

    - -

    現代人望電腦同電話時間多過望人,望得多對眼好容易攰,久而久之對隻眼都唔係咁好。有朋友就同Kelly介紹咗幾個方法,可以舒緩眼睛疲勞,真係啱晒好似Kelly呢啲日日坐Office對住電腦嘅打工仔。望遠方: 眼睛構造望遠方時比較舒服,因為睫狀肌唔需要用力,可以得到放鬆,但至少要望六公尺遠嘅距離先有效,而且除低眼鏡效果比更好...

    + +

    由香港賽馬會慈善信託基金捐助,香港仔坊會社會服務主辦的「賽馬會青創社區系列:香港仔坊會新媒體計劃」,於今日在數碼港舉辦「《英雄聯盟》青年電競比賽」決賽。大會更邀請了廣受歡迎的香港女子職業電競職業戰隊「Terrans Force」進行表演賽,精湛技巧,讓觀眾看得目不轉睛,她們更分享電競戰場上的經驗和心得。今次比賽為「賽馬...

    - 詳細 + 詳細

    - -

    香港眾志聲稱,其網頁早上被黑客入侵,頁面被修改為愛國內容。根據香港眾志在社交網站facebook貼上的相片,見到網站出現以簡體字寫上的「反港獨 撐釋法」字句。而中午過後,網頁仍無法連結。香港眾志在fb發文,以「黑客入侵眾志網頁 黃之鋒被『撐釋法』」為題,表示「今日(9月9日)香港眾志網頁被黑客入侵,導致網站停運,網站頁...

    + +

    一名醉酒中年漢凌晨踩著電動滑板車在荃灣的馬路飛馳,期間與一輛的士發生踫撞,受傷送院治理。警方事後將他拘捕,涉嫌違法駕駛動滑板車及酒後駕駛等。事發在凌晨1時許,一名44歲姓劉男子,踩著一部電動滑板車,疑逆線經過怡樂街的海濱花園對開,此時也有一輛的士沿街道駛至,兩車發生踫撞,劉飛彈上擋風玻璃再反彈墮地,頭及手受傷送院治理。...

    - 詳細 + 詳細

    - -

    教育大學內的民主牆,日前被人貼上冒犯教育局副局長蔡若蓮的標語後,民主牆凌晨被貼上涉及針對已故諾貝爾和平獎得主劉曉波及遺孀劉霞的標語。教大對民主牆再次出現冒犯性言論表示極度遺憾,予以強烈譴責,並將翻看閉路電視徹查事件,有需要時會報警處理。標語以簡體字列印,寫有「恭喜劉匪曉波魂歸西天,祝賀劉霞永被我黨軟禁」,標語於早上已被...

    + +

    律政司司長鄭若驊繼續訪問北京行程,今日拜訪司法部,就加強兩地法律界合作等議題交流意見,另亦與內地仲裁業界代表會面。她上午拜訪司法部,與司法部部長傅政華和副部長熊選國會面,就加強兩地法律界合作等議題交流意見,包括在「一帶一路」和粵港澳大灣區發展規劃下的合作契機。雙方並談及香港作為國際法律及爭議解決服務中心的優勢。她並趁機...

    - 詳細 + 詳細

    - -

    若在升降機和樓梯兩者選擇,相信大部分人都會選擇乘搭升降機,而不行樓梯。網上流傳一張圖片,見到彌敦道彌敦行大廈維修升降機,旁邊放置了數箱樽裝水,附有寫上「行樓梯上樓者,歡迎取飲品一件」的紙張。上載圖片的網民稱,「呢座大廈好識做,部lift壞左(咗),用飲品鼓勵人行樓梯」,亦有網民表示,「見到嗰箱嘢,條氣起碼順返少少」、「...

    + +

    警方於荃灣搗破一個懷疑非法麻雀賭檔,拘捕8男10女,檢獲3張電動麻雀枱、6副麻雀等。荃灣警區特別職務隊及特遣隊人員根據線報及經深入調查後,今日下午約4時展開代號「犁庭掃穴」(LEVINGTON)的打擊賭博行動,突擊搜查大河道57-75號一單位,搗破一個懷疑非法麻雀賭檔。行動中,人員拘捕一名47歲姓賀本地女子涉嫌「經營賭...

    - 詳細 + 詳細

    @@ -398,13 +402,14 @@

    大廈維修升降機 免費樽裝水 載入更多

    -
    +