Fix pip expand tests (via button, intent and double click)

1. Rename tests
  Expand to app via intent
  Expand to app via button
  Expand to max size via double click

2. Improve existing assertions

3. Create new assertions, e.g.:
    pip layer expands
    pip layer in bounds

4. Add documentation for test and assertions

Bug: 171049504
Bug: 174238218

Test: atest WMShellFlickerTests:com.android.wm.shell.flicker.pip
Change-Id: I9bfad1ef97ddc31be9d897114e23d4645b4a63d5
This commit is contained in:
Nataniel Borges
2021-08-06 12:18:54 +02:00
parent 768bdfe990
commit 645dfa7f7c
7 changed files with 445 additions and 198 deletions

View File

@@ -17,11 +17,14 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.graphics.Rect
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.os.SystemClock
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
@@ -113,25 +116,25 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
}
private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
val windowRegion = wmHelper.getWindowRegion(component)
require(!windowRegion.isEmpty) {
"Unable to find a PIP window in the current state"
}
return windowRegion.bounds
}
/**
* Expands the pip window and dismisses it by clicking on the X button.
*
* Note, currently the View coordinates reported by the accessibility are relative to
* the window, so the correct coordinates need to be calculated
*
* For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the
* dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in
* Point(716, 66), instead of Point(970, 1403)
*
* See b/179337864
*/
fun closePipWindow(wmHelper: WindowManagerStateHelper) {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
expandPipWindow(wmHelper)
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"))
requireNotNull(exitPipObject) { "PIP window dismiss button not found" }
?: error("PIP window dismiss button not found")
val dismissButtonBounds = exitPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
@@ -142,32 +145,28 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
/**
* Click once on the PIP window to expand it
* Close the pip window by pressing the expand button
*/
fun expandPipWindow(wmHelper: WindowManagerStateHelper) {
val windowRegion = wmHelper.getWindowRegion(component)
require(!windowRegion.isEmpty) {
"Unable to find a PIP window in the current state"
}
val windowRect = windowRegion.bounds
fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// Ensure WindowManagerService wait until all animations have completed
wmHelper.waitForAppTransitionIdle()
mInstrumentation.uiAutomation.syncInputTransactions()
// search and interact with the expand button
val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
val expandPipObject = uiDevice.findObject(expandSelector)
?: error("PIP window expand button not found")
val expandButtonBounds = expandPipObject.visibleBounds
uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
}
/**
* Double click on the PIP window to reopen to app
* Double click on the PIP window to expand it
*/
fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
val windowRegion = wmHelper.getWindowRegion(component)
require(!windowRegion.isEmpty) {
"Unable to find a PIP window in the current state"
}
val windowRect = windowRegion.bounds
fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
}

View File

@@ -45,7 +45,6 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
transitions {
pipApp.clickEnterPipButton(wmHelper)
pipApp.expandPipWindow(wmHelper)
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.traces.parser.toLayerName
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Test
/**
* Base class for pip expand tests
*/
abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
protected val testApp = FixedAppHelper(instrumentation)
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
* animation
*/
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
testSpec.assertWm {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks that the pip app layer remains inside the display bounds throughout the whole
* animation
*/
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
testSpec.assertLayers {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks both app windows are visible at the start of the transition (with [pipApp] on top).
* Then, during the transition, [testApp] becomes invisible and [pipApp] remains visible
*/
@Presubmit
@Test
open fun showBothAppWindowsThenHidePip() {
testSpec.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
isAppWindowVisible(testApp.component, ignoreActivity = true)
.isAppWindowOnTop(pipApp.component)
.then()
.isAppWindowInvisible(testApp.component)
.isAppWindowVisible(pipApp.component)
}
}
/**
* Checks both app layers are visible at the start of the transition. Then, during the
* transition, [testApp] becomes invisible and [pipApp] remains visible
*/
@Presubmit
@Test
open fun showBothAppLayersThenHidePip() {
testSpec.assertLayers {
isVisible(testApp.component)
.isVisible(pipApp.component)
.then()
.isInvisible(testApp.component)
.isVisible(pipApp.component)
}
}
/**
* Checks that the visible region of [testApp] plus the visible region of [pipApp]
* cover the full display area at the start of the transition
*/
@Presubmit
@Test
open fun testPlusPipAppsCoverFullScreenAtStart() {
testSpec.assertLayersStart {
val pipRegion = visibleRegion(pipApp.component).region
visibleRegion(testApp.component)
.plus(pipRegion)
.coversExactly(displayBounds)
}
}
/**
* Checks that the visible region of [pipApp] covers the full display area at the end of
* the transition
*/
@Presubmit
@Test
open fun pipAppCoversFullScreenAtEnd() {
testSpec.assertLayersEnd {
visibleRegion(pipApp.component).coversExactly(displayBounds)
}
}
/**
* Checks that the visible region of [pipApp] always expands during the animation
*/
@Presubmit
@Test
open fun pipLayerExpands() {
val layerName = pipApp.component.toLayerName()
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test expanding a pip window back to full screen via the expand button
*
* To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
*
* Actions:
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
@Postsubmit
class ExitPipViaExpandButtonClickTest(
testSpec: FlickerTestParameter
) : ExitPipToAppTransition(testSpec) {
/**
* Defines the transition used to run the test
*/
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
// launch an app behind the pip one
testApp.launchViaIntent(wmHelper)
}
}
transitions {
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
}
}
companion object {
/**
* Creates the test configurations.
*
* See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
* repetitions, screen orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
}

View File

@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -25,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,23 +31,38 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip launch and exit.
* To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
* Test expanding a pip window back to full screen via an intent
*
* To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
*
* Actions:
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen via an intent
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class EnterExitPipTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
private val testApp = FixedAppHelper(instrumentation)
class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
/**
* Defines the transition used to run the test
*/
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
// launch an app behind the pip one
testApp.launchViaIntent(wmHelper)
}
}
@@ -59,57 +72,17 @@ class EnterExitPipTest(
}
}
@Presubmit
@Test
fun pipAppRemainInsideVisibleBounds() {
testSpec.assertWm {
coversAtMost(displayBounds, pipApp.component)
}
}
@Postsubmit
@Test
fun showBothAppWindowsThenHidePip() {
testSpec.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
isAppWindowVisible(testApp.component, ignoreActivity = true)
.isAppWindowOnTop(pipApp.component)
.then()
.isAppWindowInvisible(testApp.component)
}
}
@Presubmit
@Test
fun showBothAppLayersThenHidePip() {
testSpec.assertLayers {
isVisible(testApp.component)
.isVisible(pipApp.component)
.then()
.isInvisible(testApp.component)
}
}
@Presubmit
@Test
fun testAppCoversFullScreenWithPipOnDisplay() {
testSpec.assertLayersStart {
visibleRegion(testApp.component).coversExactly(displayBounds)
visibleRegion(pipApp.component).coversAtMost(displayBounds)
}
}
@Presubmit
@Test
fun pipAppCoversFullScreen() {
testSpec.assertLayersEnd {
visibleRegion(pipApp.component).coversExactly(displayBounds)
}
}
override fun pipLayerExpands() = super.pipLayerExpands()
companion object {
/**
* Creates the test configurations.
*
* See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
* repetitions, screen orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {

View File

@@ -0,0 +1,166 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.traces.parser.toLayerName
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test expanding a pip window by double clicking it
*
* To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
*
* Actions:
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
@Postsubmit
class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
transitions {
pipApp.doubleClickPipWindow(wmHelper)
}
}
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
* animation
*/
@Test
fun pipWindowRemainInsideVisibleBounds() {
testSpec.assertWm {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks that the pip app layer remains inside the display bounds throughout the whole
* animation
*/
@Test
fun pipLayerRemainInsideVisibleBounds() {
testSpec.assertLayers {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks [pipApp] window remains visible throughout the animation
*/
@Test
fun pipWindowIsAlwaysVisible() {
testSpec.assertWm {
isAppWindowVisible(pipApp.component)
}
}
/**
* Checks [pipApp] layer remains visible throughout the animation
*/
@Test
fun pipLayerIsAlwaysVisible() {
testSpec.assertLayers {
isVisible(pipApp.component)
}
}
/**
* Checks that the visible region of [pipApp] always expands during the animation
*/
@Test
fun pipLayerExpands() {
val layerName = pipApp.component.toLayerName()
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
}
}
}
@Test
fun windowIsAlwaysPinned() {
testSpec.assertWm {
this.invoke("hasPipWindow") { it.isPinned(pipApp.component) }
}
}
/**
* Checks [pipApp] layer remains visible throughout the animation
*/
@Test
fun launcherIsAlwaysVisible() {
testSpec.assertLayers {
isVisible(LAUNCHER_COMPONENT)
}
}
/**
* Checks that the focus doesn't change between windows during the transition
*/
@FlakyTest
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
this.focusDoesNotChange()
}
}
companion object {
/**
* Creates the test configurations.
*
* See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
* repetitions, screen orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}

View File

@@ -1,113 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.pip
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip launch.
* To run this test: `atest WMShellFlickerTests:PipToAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
}
}
teardown {
eachRun {
this.setRotation(Surface.ROTATION_0)
}
}
transitions {
pipApp.expandPipWindowToApp(wmHelper)
}
}
@FlakyTest
@Test
fun appReplacesPipWindow() {
testSpec.assertWm {
this.invoke("hasPipWindow") { it.isPinned(pipApp.component) }
.isAppWindowOnTop(pipApp.component)
.then()
.invoke("hasNotPipWindow") { it.isNotPinned(pipApp.component) }
.isAppWindowOnTop(pipApp.component)
}
}
@FlakyTest
@Test
fun appReplacesPipLayer() {
testSpec.assertLayers {
this.isVisible(pipApp.component)
.isVisible(LAUNCHER_COMPONENT)
.then()
.isVisible(pipApp.component)
.isInvisible(LAUNCHER_COMPONENT)
}
}
@FlakyTest
@Test
fun testAppCoversFullScreen() {
testSpec.assertLayersStart {
visibleRegion(pipApp.component).coversExactly(displayBounds)
}
}
@FlakyTest(bugId = 151179149)
@Test
fun focusChanges() {
testSpec.assertEventLog {
this.focusChanges("NexusLauncherActivity",
pipApp.launcherName, "NexusLauncherActivity")
}
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}