Skip to content

Commit a79e003

Browse files
shai-almogclaude
andauthored
Diagnostics for iOS status-bar tap scroll-to-top (#3589) (#4868)
* simulator: add diagnostic for iOS status-bar tap scroll-to-top (#3589) Adds a Simulate -> iOS Status Bar Tap menu item that mirrors the synthesized tap (displayWidth/2, 0) dispatched by scrollViewShouldScrollToTop: in CodenameOne_GLViewController.m. It reports the responder at that coordinate plus the relevant theme constants, so users can verify on the simulator whether the device tap path would land on the built-in StatusBar button or be blocked by another component. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * iOS: expose status-bar tap counters via Display.getProperty (#3589) Records each scrollViewShouldScrollToTop: dispatch from the status-bar tap proxy and exposes the counters as Display.getProperty("cn1.iosStatusBarTap.*") so on-device code can distinguish "iOS never delivered the scroll-to-top message" from "iOS delivered it but a CodenameOne component intercepted the tap". Properties: cn1.iosStatusBarTap.count number of taps received cn1.iosStatusBarTap.lastEpochMillis ms-since-epoch of last tap cn1.iosStatusBarTap.lastX x of last synthesized tap cn1.iosStatusBarTap.lastY y of last synthesized tap cn1.iosStatusBarTap.proxyInstalled is the proxy view attached cn1.iosStatusBarTap.diagnostics all of the above as a string Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * hellocodenameone: screenshot test for iOS status-bar tap diagnostic (#3589) Adds StatusBarTapDiagnosticScreenshotTest plus a StatusBarTapDiagnosticNative interface (iOS / JavaSE / Android / JS / Win impls). Each frame in the 2x3 grid alternates "scrolled to bottom" / "tap fired -> scrolled to top", overlaid with a glass pane showing the rising tap counter. The native interface routes simulateStatusBarTap() through the same path as scrollViewShouldScrollToTop:; the body is now factored into cn1FireStatusBarTap in CodenameOne_GLViewController.m so the screenshot test exercises the real counter+pointer-dispatch on iOS without needing a UIKit status-bar event. Other platforms stub the call (or fall back to Form.pointerPressed in the JavaSE impl) and the visible scroll is applied explicitly each frame, so the screenshot is deterministic across platforms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: iOS native build for cn1FireStatusBarTap + simpler screenshot iOS build was failing: cn1FireStatusBarTap calls pointerPressedC and pointerReleasedC which are defined further down in the file. Add forward declarations so the function compiles. Replace the 2x3 animation grid with a side-by-side before/after composite. Each half is a full-height frame with a glass-pane header showing the counter, scroll position, and native-interface availability. Reads at a glance and avoids the JS-port animation-grid decode failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * hellocodenameone: skip status-bar tap test on JS port at runTest The JS port chunk-truncates large screenshot streams. The runner-level skip list addition wasn't enough (the test still ran on this branch's CI), so apply the same pattern as AbstractStickyHeaderScreenshotTest and short-circuit inside runTest with a SKIPPED status log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * hellocodenameone: add iOS/Android baselines for status-bar tap test iOS baseline shows native:yes (real cn1FireStatusBarTap counter), Android shows native:no (stub impl) — both confirm the counter rises 0->3 and the scroll position flips bottom->top across the before/after composite. Captured from PR 4868 CI artifacts (ios-ui-tests + emulator-screenshot). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dba33b4 commit a79e003

16 files changed

Lines changed: 526 additions & 8 deletions

File tree

Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4502,6 +4502,95 @@ public void actionPerformed(ActionEvent ae) {
45024502
});
45034503
simulateMenu.add(pushSim);
45044504

4505+
// Mirrors scrollViewShouldScrollToTop: in CodenameOne_GLViewController.m
4506+
// which dispatches a synthetic tap at (displayWidth/2, 0). The
4507+
// simulator otherwise can't reproduce that path because clicking the
4508+
// visible status bar just hits the bar directly.
4509+
JMenuItem statusBarTapDiag = new JMenuItem("iOS Status Bar Tap");
4510+
statusBarTapDiag.setToolTipText("Synthesizes the (displayWidth/2, 0) tap that iOS dispatches when the status bar is tapped, and reports which component would receive it.");
4511+
statusBarTapDiag.addActionListener(new ActionListener() {
4512+
@Override
4513+
public void actionPerformed(ActionEvent ae) {
4514+
Form f = Display.getInstance().getCurrent();
4515+
if (f == null) {
4516+
JOptionPane.showMessageDialog(canvas, "No current form to tap.");
4517+
return;
4518+
}
4519+
int tapX = getDisplayWidthImpl() / 2;
4520+
int tapY = 0;
4521+
StringBuilder report = new StringBuilder();
4522+
report.append("Simulating the iOS status-bar tap path.\n");
4523+
report.append("iOS native code synthesizes a tap at (displayWidth/2, 0)\n");
4524+
report.append("when scrollViewShouldScrollToTop: fires.\n\n");
4525+
report.append("Tap coordinates: (").append(tapX).append(", ").append(tapY).append(")\n\n");
4526+
4527+
UIManager um = f.getUIManager();
4528+
boolean paintsTitleBar = um.isThemeConstant("paintsTitleBarBool", false);
4529+
boolean scrollsUp = um.isThemeConstant("statusBarScrollsUpBool", true);
4530+
report.append("paintsTitleBarBool = ").append(paintsTitleBar).append("\n");
4531+
report.append("statusBarScrollsUpBool = ").append(scrollsUp).append("\n\n");
4532+
4533+
if (!paintsTitleBar) {
4534+
report.append("WARNING: paintsTitleBarBool is false. Form.createStatusBar()\n");
4535+
report.append("is not invoked, so no StatusBar component exists. Add\n");
4536+
report.append(" paintsTitleBarBool: true;\n");
4537+
report.append(" includeNativeBool: true;\n");
4538+
report.append("to your CSS #Constants block to enable the iOS behavior.\n\n");
4539+
}
4540+
if (paintsTitleBar && !scrollsUp) {
4541+
report.append("WARNING: statusBarScrollsUpBool is false. The StatusBar is\n");
4542+
report.append("created as a non-tappable Container, so no tap-to-scroll\n");
4543+
report.append("listener is wired up.\n\n");
4544+
}
4545+
4546+
Component responder = f.getResponderAt(tapX, tapY);
4547+
if (responder == null) {
4548+
report.append("No responder at (").append(tapX).append(", ").append(tapY).append(").\n");
4549+
report.append("On a device the iOS-synthesized tap will silently no-op.\n");
4550+
} else {
4551+
String uiid = responder.getUIID();
4552+
report.append("Responder at top-center:\n");
4553+
report.append(" class = ").append(responder.getClass().getName()).append("\n");
4554+
report.append(" UIID = ").append(uiid).append("\n");
4555+
com.codename1.ui.geom.Rectangle bounds = new com.codename1.ui.geom.Rectangle(
4556+
responder.getAbsoluteX(), responder.getAbsoluteY(),
4557+
responder.getWidth(), responder.getHeight());
4558+
report.append(" bounds = ").append(bounds.getX()).append(",").append(bounds.getY())
4559+
.append(" ").append(bounds.getSize().getWidth()).append("x").append(bounds.getSize().getHeight()).append("\n\n");
4560+
if ("StatusBar".equals(uiid) || "StatusBarLandscape".equals(uiid)) {
4561+
report.append("OK: this is the built-in StatusBar component. The iOS\n");
4562+
report.append("tap-to-scroll-to-top should work on a device, provided\n");
4563+
report.append("the native iOS theme is included (includeNativeBool).\n");
4564+
} else {
4565+
report.append("PROBLEM: the responder is NOT the built-in StatusBar.\n");
4566+
report.append("On an iOS device the synthesized tap will be delivered\n");
4567+
report.append("to this component instead of the scroll-to-top button,\n");
4568+
report.append("so the standard iOS gesture appears broken.\n\n");
4569+
report.append("Common causes:\n");
4570+
report.append(" - A custom component overlaps the top-center pixel\n");
4571+
report.append(" (e.g. a Toolbar side menu icon, an absolute-laid-out\n");
4572+
report.append(" PeerComponent, or a translucent overlay).\n");
4573+
report.append(" - The StatusBar UIID was zeroed out so the button has\n");
4574+
report.append(" no height, letting another component sit on top.\n");
4575+
report.append(" - Form.createStatusBar() was overridden without wiring\n");
4576+
report.append(" a tap-to-scroll listener.\n");
4577+
}
4578+
}
4579+
4580+
Log.p(report.toString());
4581+
JavaSEPort.this.pointerPressed(tapX, tapY);
4582+
JavaSEPort.this.pointerReleased(tapX, tapY);
4583+
4584+
JTextArea ta = new JTextArea(report.toString());
4585+
ta.setEditable(false);
4586+
ta.setFont(new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, 12));
4587+
JScrollPane sp = new JScrollPane(ta);
4588+
sp.setPreferredSize(new java.awt.Dimension(560, 360));
4589+
JOptionPane.showMessageDialog(canvas, sp, "iOS Status Bar Tap Diagnostic", JOptionPane.INFORMATION_MESSAGE);
4590+
}
4591+
});
4592+
simulateMenu.add(statusBarTapDiag);
4593+
45054594
if (appFrame == null) {
45064595
toolsMenu.add(componentTreeInspector);
45074596
}

Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,51 @@ - (void)layoutSubviews {
221221

222222
static CN1StatusBarTapProxyView *cn1StatusBarTapProxy = nil;
223223

224+
// Diagnostic counters for the status-bar tap-to-scroll-to-top path. Exposed
225+
// to Java via Display.getProperty("cn1.iosStatusBarTap.*") so users can
226+
// confirm whether iOS actually delivered the scroll-to-top message and
227+
// what coordinates were synthesized into CodenameOne. Useful when the
228+
// gesture appears to do nothing on a device but works in the simulator.
229+
static int cn1StatusBarTapCount = 0;
230+
static double cn1StatusBarTapLastEpochMillis = 0;
231+
static int cn1StatusBarTapLastX = -1;
232+
static int cn1StatusBarTapLastY = -1;
233+
234+
int cn1GetStatusBarTapCount() { return cn1StatusBarTapCount; }
235+
double cn1GetStatusBarTapLastEpochMillis() { return cn1StatusBarTapLastEpochMillis; }
236+
int cn1GetStatusBarTapLastX() { return cn1StatusBarTapLastX; }
237+
int cn1GetStatusBarTapLastY() { return cn1StatusBarTapLastY; }
238+
BOOL cn1IsStatusBarTapProxyInstalled() {
239+
return cn1StatusBarTapProxy != nil && cn1StatusBarTapProxy.superview != nil;
240+
}
241+
242+
// Forward declarations -- the actual definitions of pointerPressedC and
243+
// pointerReleasedC live further down in this file, but cn1FireStatusBarTap
244+
// (defined immediately below so it sits next to the static counter state it
245+
// drives) needs to call them.
246+
extern void pointerPressedC(int* x, int* y, int length);
247+
extern void pointerReleasedC(int* x, int* y, int length);
248+
249+
// Fires the same diagnostic-counter bump and synthesized pointer event the
250+
// scrollViewShouldScrollToTop: delegate dispatches. Exposed so an
251+
// instrumented native interface can drive the path from a screenshot test
252+
// without waiting for a real status-bar tap.
253+
void cn1FireStatusBarTap() {
254+
int xArray[1];
255+
int yArray[1];
256+
xArray[0] = displayWidth / 2;
257+
yArray[0] = 0;
258+
cn1StatusBarTapCount++;
259+
cn1StatusBarTapLastEpochMillis = [[NSDate date] timeIntervalSince1970] * 1000.0;
260+
cn1StatusBarTapLastX = xArray[0];
261+
cn1StatusBarTapLastY = yArray[0];
262+
pointerPressedC(xArray, yArray, 1);
263+
pointerReleasedC(xArray, yArray, 1);
264+
if (cn1StatusBarTapProxy != nil) {
265+
cn1StatusBarTapProxy.contentOffset = CGPointMake(0, 1);
266+
}
267+
}
268+
224269

225270
// 1 for portrait lock, and 2 for landscape lock
226271
int orientationLock = 0;
@@ -2023,13 +2068,7 @@ - (void)cn1InstallStatusBarTapProxy {
20232068

20242069
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
20252070
if (scrollView == cn1StatusBarTapProxy) {
2026-
int xArray[1];
2027-
int yArray[1];
2028-
xArray[0] = displayWidth / 2;
2029-
yArray[0] = 0;
2030-
pointerPressedC(xArray, yArray, 1);
2031-
pointerReleasedC(xArray, yArray, 1);
2032-
cn1StatusBarTapProxy.contentOffset = CGPointMake(0, 1);
2071+
cn1FireStatusBarTap();
20332072
return NO;
20342073
}
20352074
return YES;

Ports/iOSPort/nativeSources/IOSNative.m

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4692,6 +4692,32 @@ JAVA_OBJECT com_codename1_impl_ios_IOSNative_getDeviceName__(CN1_THREAD_STATE_MU
46924692
return fromNSString(CN1_THREAD_STATE_PASS_ARG [[UIDevice currentDevice] name]);
46934693
}
46944694

4695+
extern int cn1GetStatusBarTapCount();
4696+
extern double cn1GetStatusBarTapLastEpochMillis();
4697+
extern int cn1GetStatusBarTapLastX();
4698+
extern int cn1GetStatusBarTapLastY();
4699+
extern BOOL cn1IsStatusBarTapProxyInstalled();
4700+
4701+
JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapCount__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
4702+
return (JAVA_INT)cn1GetStatusBarTapCount();
4703+
}
4704+
4705+
JAVA_LONG com_codename1_impl_ios_IOSNative_getStatusBarTapLastEpochMillis__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
4706+
return (JAVA_LONG)cn1GetStatusBarTapLastEpochMillis();
4707+
}
4708+
4709+
JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastX__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
4710+
return (JAVA_INT)cn1GetStatusBarTapLastX();
4711+
}
4712+
4713+
JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastY__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
4714+
return (JAVA_INT)cn1GetStatusBarTapLastY();
4715+
}
4716+
4717+
JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isStatusBarTapProxyInstalled__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
4718+
return cn1IsStatusBarTapProxyInstalled() ? JAVA_TRUE : JAVA_FALSE;
4719+
}
4720+
46954721
JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isGoodLocation___long(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_LONG peer) {
46964722
POOL_BEGIN();
46974723
CLLocationManager* l = (BRIDGE_CAST CLLocationManager*)((void *)peer);
@@ -9250,6 +9276,26 @@ JAVA_OBJECT com_codename1_impl_ios_IOSNative_getDeviceName___R_java_lang_String(
92509276
return com_codename1_impl_ios_IOSNative_getDeviceName__(CN1_THREAD_STATE_PASS_ARG instanceObject);
92519277
}
92529278

9279+
JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapCount___R_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
9280+
return com_codename1_impl_ios_IOSNative_getStatusBarTapCount__(CN1_THREAD_STATE_PASS_ARG instanceObject);
9281+
}
9282+
9283+
JAVA_LONG com_codename1_impl_ios_IOSNative_getStatusBarTapLastEpochMillis___R_long(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
9284+
return com_codename1_impl_ios_IOSNative_getStatusBarTapLastEpochMillis__(CN1_THREAD_STATE_PASS_ARG instanceObject);
9285+
}
9286+
9287+
JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastX___R_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
9288+
return com_codename1_impl_ios_IOSNative_getStatusBarTapLastX__(CN1_THREAD_STATE_PASS_ARG instanceObject);
9289+
}
9290+
9291+
JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastY___R_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
9292+
return com_codename1_impl_ios_IOSNative_getStatusBarTapLastY__(CN1_THREAD_STATE_PASS_ARG instanceObject);
9293+
}
9294+
9295+
JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isStatusBarTapProxyInstalled___R_boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
9296+
return com_codename1_impl_ios_IOSNative_isStatusBarTapProxyInstalled__(CN1_THREAD_STATE_PASS_ARG instanceObject);
9297+
}
9298+
92539299
JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isGoodLocation___long_R_boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_LONG peer) {
92549300
return com_codename1_impl_ios_IOSNative_isGoodLocation___long(CN1_THREAD_STATE_PASS_ARG instanceObject, peer);
92559301
}

Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6488,7 +6488,36 @@ public void error(Throwable t) {
64886488
if(key.equalsIgnoreCase("UDID")) {
64896489
return nativeInstance.getUDID();
64906490
}
6491-
6491+
if("cn1.iosStatusBarTap.count".equals(key)) {
6492+
return String.valueOf(nativeInstance.getStatusBarTapCount());
6493+
}
6494+
if("cn1.iosStatusBarTap.lastEpochMillis".equals(key)) {
6495+
return String.valueOf(nativeInstance.getStatusBarTapLastEpochMillis());
6496+
}
6497+
if("cn1.iosStatusBarTap.lastX".equals(key)) {
6498+
return String.valueOf(nativeInstance.getStatusBarTapLastX());
6499+
}
6500+
if("cn1.iosStatusBarTap.lastY".equals(key)) {
6501+
return String.valueOf(nativeInstance.getStatusBarTapLastY());
6502+
}
6503+
if("cn1.iosStatusBarTap.proxyInstalled".equals(key)) {
6504+
return String.valueOf(nativeInstance.isStatusBarTapProxyInstalled());
6505+
}
6506+
if("cn1.iosStatusBarTap.diagnostics".equals(key)) {
6507+
int count = nativeInstance.getStatusBarTapCount();
6508+
long lastTime = nativeInstance.getStatusBarTapLastEpochMillis();
6509+
int lastX = nativeInstance.getStatusBarTapLastX();
6510+
int lastY = nativeInstance.getStatusBarTapLastY();
6511+
boolean installed = nativeInstance.isStatusBarTapProxyInstalled();
6512+
StringBuilder sb = new StringBuilder();
6513+
sb.append("count=").append(count);
6514+
sb.append(", lastEpochMillis=").append(lastTime);
6515+
sb.append(", lastX=").append(lastX);
6516+
sb.append(", lastY=").append(lastY);
6517+
sb.append(", proxyInstalled=").append(installed);
6518+
return sb.toString();
6519+
}
6520+
64926521
return super.getProperty(key, defaultValue);
64936522
}
64946523

Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,17 @@ byte[] loadResource(String name, String type) {
324324
native String getUDID();
325325
native String getOSVersion();
326326
native String getDeviceName();
327+
328+
// Diagnostics for the status-bar tap-to-scroll-to-top path. Surfaced to
329+
// user code via Display.getProperty("cn1.iosStatusBarTap.*") in
330+
// IOSImplementation. Lets developers detect on-device whether iOS is
331+
// delivering the scroll-to-top message at all when the gesture does
332+
// nothing visibly.
333+
native int getStatusBarTapCount();
334+
native long getStatusBarTapLastEpochMillis();
335+
native int getStatusBarTapLastX();
336+
native int getStatusBarTapLastY();
337+
native boolean isStatusBarTapProxyInstalled();
327338

328339
// location manager
329340
native boolean isGPSEnabled();
28.2 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.codenameone.examples.hellocodenameone;
2+
3+
public class StatusBarTapDiagnosticNativeImpl {
4+
public boolean simulateStatusBarTap() {
5+
return false;
6+
}
7+
8+
public int getTapCount() {
9+
return 0;
10+
}
11+
12+
public boolean isSupported() {
13+
return false;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.codenameone.examples.hellocodenameone;
2+
3+
import com.codename1.system.NativeInterface;
4+
5+
public interface StatusBarTapDiagnosticNative extends NativeInterface {
6+
/// Fires the same path the iOS scrollViewShouldScrollToTop: delegate runs:
7+
/// bump the native counter and synthesize a pointer event at
8+
/// (displayWidth/2, 0). On platforms that don't have the iOS proxy view
9+
/// the impl falls back to dispatching the pointer event to the current
10+
/// Form so the screenshot test produces the same visual progression.
11+
boolean simulateStatusBarTap();
12+
13+
int getTapCount();
14+
}

scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ private static int testTimeoutMs() {
8484
new StickyHeaderSlideTransitionScreenshotTest(),
8585
new StickyHeaderFadeTransitionScreenshotTest(),
8686
new TensileBounceScreenshotTest(),
87+
new StatusBarTapDiagnosticScreenshotTest(),
8788
new ComponentReplaceFadeScreenshotTest(),
8889
new ComponentReplaceSlideScreenshotTest(),
8990
new ComponentReplaceFlipScreenshotTest(),
@@ -292,6 +293,7 @@ private static boolean isJsSkippedScreenshotTest(String testName) {
292293
return "KotlinUiTest".equals(testName)
293294
|| "MainScreenScreenshotTest".equals(testName)
294295
|| "SheetScreenshotTest".equals(testName)
296+
|| "StatusBarTapDiagnosticScreenshotTest".equals(testName)
295297
|| "ImageViewerNavigationScreenshotTest".equals(testName)
296298
|| "TabsScreenshotTest".equals(testName)
297299
|| "TextAreaAlignmentScreenshotTest".equals(testName)

0 commit comments

Comments
 (0)