Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions .agents/skills/run_tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ If you have a specific failing test (e.g., `BasicTextFieldTest#longText_doesNotC

1. **Find the Test File**: Use code search, `find_declaration`, or `find_files` (e.g., search for `BasicTextFieldTest.kt`).
2. **Identify the Module**: The file path determines the Gradle project (e.g., `compose/foundation/foundation/src/...` belongs to `:compose:foundation:foundation`).
3. **Identify Test Type**:
3. **Identify Test Type**:
* If the test is in a `test`, `androidHostTest`, or `jvmTest` folder, it is a Unit Test.
* If the test is in `androidTest` or `androidDeviceTest`, it is an Instrumentation Test.
4. **Identify Module Type**: Check if the module is KMP or standard Android (see details below) by inspecting `build.gradle` for `androidXMultiplatform {` or running `./gradlew <module>:tasks | grep "test"`.
Expand Down Expand Up @@ -104,7 +104,7 @@ To verify a flaky test, you can run it multiple times on Firebase Test Lab using
* Add the companion object for data generation:
```kotlin
import org.junit.runners.Parameterized

companion object {
private const val RUNS = 100 // Adjust as needed
@JvmStatic
Expand All @@ -121,4 +121,51 @@ To verify a flaky test, you can run it multiple times on Firebase Test Lab using
```bash
# Example for KMP (append 'releaseAndroidTest' instead of 'androidDeviceTest' for standard Android)
./gradlew <project-name>:ftlOnApisandroidDeviceTest --testTimeout=1h --api 28 --api 30 --className=androidx.example.MyTest
```
```

## 6. Screenshot / Visual Tests

Screenshot tests (e.g., using `AndroidXScreenshotTestRule`) compare the rendered UI against approved "golden" reference images to detect visual regressions.

* **Emulator Requirement**: Screenshot tests must be executed on a specific emulator configuration to ensure consistent rendering. Locally, you **must** use a **Medium Phone API 35** emulator.
* **Managing the Emulator**: Use the [android-cli](https://developer.android.com/tools/agents/android-cli) skill to manage and run the emulator.
* To list available virtual devices:
```bash
android emulator list
```
* To launch the required emulator:
```bash
android emulator start <device_name>
```
* **Running the Tests**: Once the emulator is running and connected, execute the screenshot tests as standard instrumentation tests using Gradle (see Section 4).
* **Updating Screenshot Goldens**:
If a screenshot test fails due to intentional UI changes, you must update the golden reference images in the `support-goldens` repository (which is checked out as a sibling `golden` directory to `frameworks/support`).

1. **Run Tests (Trigger Failure)**: Execute the screenshot tests locally on the **Medium Phone API 35** emulator. If you are introducing a new screenshot or expecting a change, the test must fail to write output files. If the test passes but you want to recreate the golden, delete the local golden image first to trigger a `MISSING_REFERENCE` failure.
2. **Pull Test Outputs**: When a test fails, `ScreenshotTestRule` writes the actual screenshot, expected screenshot, and a mapping `.textproto` file to the device. The output directory is defined in `ScreenshotTestRule.kt` as:
```kotlin
val deviceOutputDirectory
get() =
File(
InstrumentationRegistry.getInstrumentation().getContext().externalCacheDir,
"androidx_screenshots",
)
```
To pull these output files to your workstation, run:
```bash
adb pull /sdcard/Android/data/<test_package_name>/cache/androidx_screenshots/
```
*(Note: `<test_package_name>` is the identifier of the test APK, e.g., `androidx.compose.material.test`)*
3. **Map and Copy to Goldens Repo**: Locate the generated `*_diffResult_goldResult.textproto` file for the failed test. It explicitly maps the actual screenshot filename on the device to its repo destination. For example:
- `image_location_test`: `"androidx.compose.material.CheckboxScreenshotTest_checkBoxTest_checked_emulator_b294d33ecd4f4764_actual.png"`
- `image_location_golden`: `"compose/material/material/checkbox_checked_emulator.png"`

Rename the pulled `*_actual.png` image to match the filename in `image_location_golden` (e.g., `checkbox_checked_emulator.png`) and copy it to its location in the sibling `golden` project repository (e.g., `../../golden/compose/material/material/`).
4. **Submit Linked CLs via Shared Topic**:
To submit your code changes and golden image updates together (ensuring presubmit runs them as a unit), upload them to Gerrit using a **shared topic**:
- Code CL (under `frameworks/support`): `repo upload --cbr -t <my_shared_topic>`
- Golden CL (under `golden/`): `repo upload --cbr -t <my_shared_topic>`

> [!NOTE]
> Some modules may use custom wrapper rules (such as `RemoteScreenshotTestRule` in `wear/compose/remote/remote-material3`). These custom rules delegate to the same core `ScreenshotTestRule` library under the hood. They write their outputs to the same `androidx_screenshots` cache directory on the device and generate identical `.textproto` mapping files.

3 changes: 2 additions & 1 deletion a2ui/a2ui-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ plugins {
}

dependencies {
api(project(":a2ui:a2ui-core"))
api(project(":a2ui:a2ui-model"))
implementation(project(":a2ui:a2ui-engine"))

api("androidx.compose.runtime:runtime:1.10.0")

Expand Down
167 changes: 0 additions & 167 deletions a2ui/a2ui-core/api/current.txt
Original file line number Diff line number Diff line change
@@ -1,171 +1,4 @@
// Signature format: 4.0
package androidx.a2ui.core.catalog {

public interface A2uiCoreCatalog {
}

}

package androidx.a2ui.core.model {

public final class A2uiSurfaceGroupModel {
method @InaccessibleFromKotlin public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.a2ui.core.model.A2uiSurfaceModel>> getActiveSurfaces();
property public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.a2ui.core.model.A2uiSurfaceModel>> activeSurfaces;
}

public final class A2uiSurfaceModel {
ctor @BytecodeOnly public A2uiSurfaceModel(String!, androidx.a2ui.core.catalog.A2uiCoreCatalog!, androidx.a2ui.core.platform.A2uiCoreDataModel!, androidx.a2ui.core.platform.A2uiComponentRegistry!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function1!, java.util.Map!, boolean, kotlin.jvm.functions.Function0!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
ctor public A2uiSurfaceModel(String id, androidx.a2ui.core.catalog.A2uiCoreCatalog catalog, androidx.a2ui.core.platform.A2uiCoreDataModel dataModel, androidx.a2ui.core.platform.A2uiComponentRegistry componentRegistry, kotlin.jvm.functions.Function1<? super androidx.a2ui.core.protocol.A2uiUserAction,kotlin.Unit> onDispatchAction, kotlin.jvm.functions.Function1<? super androidx.a2ui.core.protocol.A2uiClientError,kotlin.Unit> onDispatchError, optional java.util.Map<java.lang.String,? extends java.lang.Object?> theme, optional boolean shouldSendDataModel, optional kotlin.jvm.functions.Function0<java.lang.Long> timeProvider);
method public void dispatchAction(String componentId, java.util.Map<java.lang.String,? extends java.lang.Object?> actionDefinition);
method public void dispatchError(String componentId, androidx.a2ui.core.protocol.A2uiException exception);
method @InaccessibleFromKotlin public androidx.a2ui.core.catalog.A2uiCoreCatalog getCatalog();
method @InaccessibleFromKotlin public androidx.a2ui.core.platform.A2uiComponentRegistry getComponentRegistry();
method @InaccessibleFromKotlin public androidx.a2ui.core.platform.A2uiCoreDataModel getDataModel();
method @InaccessibleFromKotlin public String getId();
method @InaccessibleFromKotlin public java.util.Map<java.lang.String,java.lang.Object?> getTheme();
method @InaccessibleFromKotlin public boolean shouldSendDataModel();
property public androidx.a2ui.core.catalog.A2uiCoreCatalog catalog;
property public androidx.a2ui.core.platform.A2uiComponentRegistry componentRegistry;
property public androidx.a2ui.core.platform.A2uiCoreDataModel dataModel;
property public String id;
property public boolean shouldSendDataModel;
property public java.util.Map<java.lang.String,java.lang.Object?> theme;
}

}

package androidx.a2ui.core.platform {

public interface A2uiComponentRegistry {
method public void dispose();
method public void reportError(String id, androidx.a2ui.core.protocol.A2uiException exception);
method public void update(java.util.List<androidx.a2ui.core.protocol.A2uiComponentPayload> components);
}

public interface A2uiCoreDataModel {
method public void dispose();
method public operator Object? get(androidx.a2ui.core.protocol.A2uiDataPath path);
method public void update(androidx.a2ui.core.protocol.A2uiDataPath path, Object? value);
}

}

package androidx.a2ui.core.protocol {

public final class A2uiClientError implements androidx.a2ui.core.protocol.A2uiClientToServerMessage {
ctor @BytecodeOnly public A2uiClientError(String!, String!, String!, java.util.Map!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
ctor public A2uiClientError(String code, String surfaceId, String message, optional java.util.Map<java.lang.String,? extends java.lang.Object?> context);
method @InaccessibleFromKotlin public String getCode();
method @InaccessibleFromKotlin public java.util.Map<java.lang.String,java.lang.Object?> getContext();
method @InaccessibleFromKotlin public String getMessage();
method @InaccessibleFromKotlin public String getSurfaceId();
property public String code;
property public java.util.Map<java.lang.String,java.lang.Object?> context;
property public String message;
property public String surfaceId;
}

public sealed exhaustive interface A2uiClientToServerMessage {
}

public final class A2uiComponentPayload {
ctor public A2uiComponentPayload(String id, String type, java.util.Map<java.lang.String,? extends java.lang.Object?> properties);
method @InaccessibleFromKotlin public String getId();
method @InaccessibleFromKotlin public java.util.Map<java.lang.String,java.lang.Object?> getProperties();
method @InaccessibleFromKotlin public String getType();
property public String id;
property public java.util.Map<java.lang.String,java.lang.Object?> properties;
property public String type;
}

public final class A2uiCreateSurfaceMessage implements androidx.a2ui.core.protocol.A2uiServerToClientMessage {
ctor @BytecodeOnly public A2uiCreateSurfaceMessage(String!, String!, java.util.Map!, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker!);
ctor public A2uiCreateSurfaceMessage(String surfaceId, String catalogId, optional java.util.Map<java.lang.String,? extends java.lang.Object?> theme, optional boolean shouldSendDataModel);
method @InaccessibleFromKotlin public String getCatalogId();
method @InaccessibleFromKotlin public String getSurfaceId();
method @InaccessibleFromKotlin public java.util.Map<java.lang.String,java.lang.Object?> getTheme();
method @InaccessibleFromKotlin public boolean shouldSendDataModel();
property public String catalogId;
property public boolean shouldSendDataModel;
property public String surfaceId;
property public java.util.Map<java.lang.String,java.lang.Object?> theme;
}

public final class A2uiDataPath {
ctor public A2uiDataPath(String path);
method @InaccessibleFromKotlin public String getNormalizedPath();
method @InaccessibleFromKotlin public String getPath();
method @InaccessibleFromKotlin public java.util.List<java.lang.String> getSegments();
method @InaccessibleFromKotlin public boolean isAbsolute();
property public boolean isAbsolute;
property public String normalizedPath;
property public String path;
property public java.util.List<java.lang.String> segments;
}

public final class A2uiDeleteSurfaceMessage implements androidx.a2ui.core.protocol.A2uiServerToClientMessage {
ctor public A2uiDeleteSurfaceMessage(String surfaceId);
method @InaccessibleFromKotlin public String getSurfaceId();
property public String surfaceId;
}

public abstract sealed exhaustive class A2uiException extends java.lang.Exception {
method @InaccessibleFromKotlin public final String getCode();
method @InaccessibleFromKotlin public final java.util.Map<java.lang.String,java.lang.Object?> getContext();
property public final String code;
property public final java.util.Map<java.lang.String,java.lang.Object?> context;
}

public static final class A2uiException.A2uiRuntimeException extends androidx.a2ui.core.protocol.A2uiException {
ctor @BytecodeOnly public A2uiException.A2uiRuntimeException(String!, java.util.Map!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
ctor public A2uiException.A2uiRuntimeException(String message, optional java.util.Map<java.lang.String,? extends java.lang.Object?> context);
}

public static final class A2uiException.A2uiValidationException extends androidx.a2ui.core.protocol.A2uiException {
ctor public A2uiException.A2uiValidationException(String message, String path);
}

public sealed nonexhaustive interface A2uiServerToClientMessage {
method @InaccessibleFromKotlin public String getSurfaceId();
property public abstract String surfaceId;
}

public final class A2uiUpdateComponentsMessage implements androidx.a2ui.core.protocol.A2uiServerToClientMessage {
ctor public A2uiUpdateComponentsMessage(String surfaceId, java.util.List<androidx.a2ui.core.protocol.A2uiComponentPayload> components);
method @InaccessibleFromKotlin public java.util.List<androidx.a2ui.core.protocol.A2uiComponentPayload> getComponents();
method @InaccessibleFromKotlin public String getSurfaceId();
property public java.util.List<androidx.a2ui.core.protocol.A2uiComponentPayload> components;
property public String surfaceId;
}

public final class A2uiUpdateDataModelMessage implements androidx.a2ui.core.protocol.A2uiServerToClientMessage {
ctor public A2uiUpdateDataModelMessage(String surfaceId, optional String path, optional Object? value);
ctor @BytecodeOnly public A2uiUpdateDataModelMessage(String!, String!, Object!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
method @InaccessibleFromKotlin public String getPath();
method @InaccessibleFromKotlin public String getSurfaceId();
method @InaccessibleFromKotlin public Object? getValue();
property public String path;
property public String surfaceId;
property public Object? value;
}

public final class A2uiUserAction implements androidx.a2ui.core.protocol.A2uiClientToServerMessage {
ctor @BytecodeOnly public A2uiUserAction(String!, String!, String!, long, java.util.Map!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
ctor public A2uiUserAction(String type, String surfaceId, String componentId, long timestamp, optional java.util.Map<java.lang.String,? extends java.lang.Object?> context);
method @InaccessibleFromKotlin public String getComponentId();
method @InaccessibleFromKotlin public java.util.Map<java.lang.String,java.lang.Object?> getContext();
method @InaccessibleFromKotlin public String getSurfaceId();
method @InaccessibleFromKotlin public long getTimestamp();
method @InaccessibleFromKotlin public String getType();
property public String componentId;
property public java.util.Map<java.lang.String,java.lang.Object?> context;
property public String surfaceId;
property public long timestamp;
property public String type;
}

}

package androidx.a2ui.core.schema {

public final class A2uiAllOfSchema extends androidx.a2ui.core.schema.A2uiSchema {
Expand Down
Loading
Loading