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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ back door to the app. Details:
which locale reads which language's documents. Do not copy that table into the test.
- The tablet set goes into both tablet slots, because Play falls back to the phone set only
where a slot is empty.
- The edit is taken twice. `04-edit-lite` locks the strip from the test, because the run is
a pro build, and `store_screenshots.py --app lite` stages it in place of `04-edit`.
- In `release.yml` the listing is not gated on the screenshots. Do not put them back into
a plain `needs:`, or a wedged emulator takes the listing text down with it.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import app.opendocument.droid.ui.OpenFileIdling
import app.opendocument.droid.ui.activity.DocumentFragment
import app.opendocument.droid.ui.activity.MainActivity
import app.opendocument.droid.ui.widget.DocumentActions
import app.opendocument.droid.ui.widget.EditingTools
import app.opendocument.droid.ui.widget.PageView
import java.io.File
import java.io.FileOutputStream
Expand All @@ -62,9 +63,10 @@ import org.junit.runner.RunWith
* The pictures the play store shows.
*
* Six screens, in every locale the listing is written in, on whichever device the runner is
* driving. `scripts/frame-screenshots.py` then frames what this writes and
* `scripts/store_screenshots.py` checks the set and stages it for supply - the names below are the
* names those expect, and their order is the order the store shows them in.
* driving. The edit is taken twice, the second time as lite shows it - see [editing].
* `scripts/frame-screenshots.py` then frames what this writes and `scripts/store_screenshots.py`
* checks the set and stages it for supply - the names below are the names those expect, and their
* order is the order the store shows them in.
*
* Which locales those are, and which language's documents each of them reads, comes out of
* `screenshot-names.json` beside the samples: `scripts/store_screenshots.py` holds that table and
Expand Down Expand Up @@ -165,7 +167,8 @@ class ScreenshotTests {

landing(locale)
searching(locale, folder.getValue("text"), words.getString("search"))
editing(locale, folder.getValue("text"))
editing(locale, folder.getValue("text"), Shot.EDIT)
editing(locale, folder.getValue("text"), Shot.EDIT_LITE)
document(locale, Shot.SHEET, folder.getValue("sheet"))
document(locale, Shot.PDF, folder.getValue("paper"))
document(locale, Shot.OFFICE, folder.getValue("word"))
Expand Down Expand Up @@ -201,12 +204,16 @@ class ScreenshotTests {
/**
* The same text document, being edited, with the keyboard up.
*
* [Shot.EDIT_LITE] is the edit as lite shows it: pro's tools dimmed behind its badge. The run
* is a pro build, so the test locks the strip itself, before the tap - the selection the tap
* makes then reaches the locked strip as it does in lite. Nothing in the app knows about it.
*
* The keyboard needs a real tap: WebKit raises it for a gesture it saw, so an edit staged
* entirely in code sets a caret and nothing else. Where the text is depends on the page, so
* this works down the page rather than betting the run on one offset - the sample is a page of
* A4 and a tap into its margin reaches nothing.
*/
private fun editing(locale: String, uri: Uri) {
private fun editing(locale: String, uri: Uri, shot: Shot) {
val activity = launchWith(uri)
val fragment = documentFragment(activity)

Expand All @@ -224,6 +231,16 @@ class ScreenshotTests {
waitFor(EDIT_TIMEOUT_MS) { isEditable(pageView) },
)

// after the page turned editable, so nothing of the edit mode's own start lays the
// strip out again over it
if (shot == Shot.EDIT_LITE) {
instrumentation.runOnMainSync {
activity
.findViewById<EditingTools>(R.id.editing_tools)
.showFormatting(sheet = false, locked = true)
}
}

Assert.assertTrue(
"no tap down the page set a caret, so the keyboard never came up",
KEYBOARD_OFFSETS.any { offset ->
Expand All @@ -239,7 +256,7 @@ class ScreenshotTests {
// keyboard halfway up
settle()

shoot(locale, Shot.EDIT)
shoot(locale, shot)
finish()
}

Expand Down Expand Up @@ -653,6 +670,9 @@ class ScreenshotTests {
TEXT("02-text"),
SHEET("03-sheet"),
EDIT("04-edit"),

/** Lite's in place of [EDIT]; `store_screenshots.py` stages it for the lite listing. */
EDIT_LITE("04-edit-lite"),
PDF("05-pdf"),
OFFICE("06-office"),
}
Expand Down
19 changes: 13 additions & 6 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,23 @@ end
# captured has to have all of it: half a set is worse in the store than the set
# already up there, which is what the script checks before it copies anything.
#
# Each flavor gets its own set: lite's edit shows pro's tools locked.
#
# @return whether there were any, which is also what tells supply to replace what
# the store has rather than leave it alone.
def stage_screenshots(staged)
def stage_screenshots(staged, flavor)
captured = !Dir.glob(File.join(FRAMED_DIR, "*", "*.png")).empty?
unless captured
UI.important("no screenshots under #{FRAMED_DIR} - writing the listing text only")
return false
end

ok = system("python3", STORE_SCREENSHOTS, "--screenshots", FRAMED_DIR, "--stage", staged)
ok = system(
"python3", STORE_SCREENSHOTS,
"--screenshots", FRAMED_DIR,
"--stage", staged,
"--app", flavor.downcase
)
UI.user_error!("the screenshots could not be staged - see above") unless ok

true
Expand Down Expand Up @@ -246,7 +253,7 @@ platform :android do
version = require_version(options[:version])

staged = stage_listing(flavor, version)
captured = stage_screenshots(staged)
captured = stage_screenshots(staged, flavor)

upload_to_play_store(
track: options[:track] || DEFAULT_TRACK,
Expand Down Expand Up @@ -321,9 +328,9 @@ platform :android do
FileUtils.rm_rf(ADDITIONAL_OUTPUT)

# The Pro flavor: it links no ad sdk, so no consent form can come up in front
# of a picture. The two apps are the same app, and what differs - the banner
# Lite carries - is not in a screenshot either way, so one set of pictures
# goes to both listings.
# of a picture. The banner Lite carries is in no screenshot. The edit is the
# one screen that differs, so the test takes it a second time with the tools
# locked, and each listing gets its own set.
gradle(
task: "connectedProDebugAndroidTest",
properties: {
Expand Down
3 changes: 3 additions & 0 deletions scripts/frame-screenshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,9 @@ def main(argv=None):
# the capture run writes the device into the name, being the only
# thing that knows which emulator it was driving
device, name = store.named(path.stem)
# lite's edit is framed as the screen it stands in for: the same
# headline, and the same place on the line that crosses the set
name = next((pro for pro, lite in store.LITE.items() if lite == name), name)
if device is None or name not in screens:
print(f"{locale}: skipping {path.name}, which no screen is named after",
file=sys.stderr)
Expand Down
45 changes: 34 additions & 11 deletions scripts/store_screenshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# scripts/store_screenshots.py --languages what to capture
# scripts/store_screenshots.py check what was captured
# scripts/store_screenshots.py --stage DIR check it and stage it
# scripts/store_screenshots.py --stage DIR --app A check it and stage A's set
#
# An underscore in the name, where every other script here has a dash:
# `frame-screenshots.py` imports this one, and a dash cannot be imported.
Expand Down Expand Up @@ -68,6 +68,19 @@
"06-office",
)

# What lite shows in place of a screen of pro's. The edit is the one screen where
# the two apps differ: lite dims pro's tools behind a badge. The run is a pro
# build, and `ScreenshotTests.kt` takes the edit a second time with the tools
# locked.
LITE = {
"04-edit": "04-edit-lite",
}

APPS = ("pro", "lite")

# Every picture a run takes.
CAPTURED = SCREENS + tuple(LITE.values())

# The devices photographed, and the directories supply uploads each one to. Play
# keeps a set per form factor and shows the phone one everywhere it has nothing
# better, so the tablet set is what makes the listing a tablet listing.
Expand Down Expand Up @@ -140,7 +153,7 @@ def named(stem):
picture is the size of its canvas, and two devices could share one.
"""
for device in DIRECTORIES:
for screen in SCREENS:
for screen in CAPTURED:
if stem == f"{device}-{screen}":
return device, screen

Expand Down Expand Up @@ -187,7 +200,7 @@ def collect(directory):
if device is None:
problems.append(
f"{locale}: {path.name} is not one of "
+ ", ".join([f"{d}-{s}" for d in DIRECTORIES for s in SCREENS] + [FEATURE])
+ ", ".join([f"{d}-{s}" for d in DIRECTORIES for s in CAPTURED] + [FEATURE])
)
continue

Expand All @@ -205,7 +218,7 @@ def collect(directory):
pictures.setdefault(device, {})[screen] = path

for device in DIRECTORIES:
missing = [screen for screen in SCREENS if screen not in pictures.get(device, {})]
missing = [screen for screen in CAPTURED if screen not in pictures.get(device, {})]
if missing:
problems.append(f"{locale}: no {device} {', '.join(missing)}")

Expand All @@ -217,8 +230,8 @@ def collect(directory):
return found, features, problems


def stage(found, features, directory):
"""Write the pictures into the metadata tree supply uploads.
def stage(found, features, directory, app):
"""Write `app`'s pictures into the metadata tree supply uploads.

Into the same directory `scripts/store-listing.py` stages the text in, under
the `images/` subdirectory supply reads a locale's pictures from - so one
Expand All @@ -227,7 +240,8 @@ def stage(found, features, directory):

The borrowed locales are copied from the English rather than left out: what
supply does not upload for a locale, play keeps - which would be whatever was
there before this release.
there before this release. Lite's pictures go under the name of the screen
they stand in for, so they keep its place in the store.
"""
directory = Path(directory)

Expand All @@ -241,8 +255,9 @@ def stage(found, features, directory):
for name in DIRECTORIES[device]:
folder = directory / locale / "images" / name
folder.mkdir(parents=True, exist_ok=True)
for screen, path in screens.items():
shutil.copyfile(path, folder / f"{screen}.png")
for screen in SCREENS:
picture = LITE.get(screen, screen) if app == "lite" else screen
shutil.copyfile(screens[picture], folder / f"{screen}.png")

for locale in borrowed():
source = directory / FALLBACK / "images"
Expand Down Expand Up @@ -282,12 +297,20 @@ def main(argv=None):
metavar="DIR",
help="also write the screenshots into the supply metadata tree in DIR",
)
parser.add_argument(
"--app",
choices=APPS,
help="whose set --stage writes: lite's edit is not pro's",
)
args = parser.parse_args(argv)

if args.languages:
print("\n".join(languages()))
return 0

if args.stage and not args.app:
return fail("--stage needs --app, because the two listings differ in the edit")

if not LEAST <= len(SCREENS) <= MOST:
return fail(f"play takes {LEAST} to {MOST} screenshots per device, not {len(SCREENS)}")

Expand All @@ -302,11 +325,11 @@ def main(argv=None):

if args.stage:
try:
stage(found, features, args.stage)
stage(found, features, args.stage, args.app)
except OSError as reason:
return fail(str(reason))
print(
f"staged {len(SCREENS)} screenshots per device and a feature graphic for "
f"staged {args.app}'s {len(SCREENS)} screenshots per device and a feature graphic for "
f"{len(found) + len(borrowed())} locales in {args.stage}"
)
else:
Expand Down
Loading