~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
102 lines · 3.8 KB Raw History
import XCTest

/// Draws the screens `ios/look.mjs` asks for. It's that script's hands, not a
/// test: with no plan it's skipped.
final class LookTests: XCTestCase {
    private struct Plan: Decodable {
        let answers: String
        let misses: String
        let now: String
        let signedIn: Bool
        let runs: [Run]
    }

    private struct Run: Decodable {
        let route: String
        let scheme: String
        let theme: String?
        let steps: [Step]
        let top: Bool
        let out: String
    }

    private struct Step: Decodable {
        var tap: String?
        var type: String?
        var swipe: String?
    }

    func testLook() throws {
        guard let path = ProcessInfo.processInfo.environment["KNOTLESS_LOOK"] else {
            throw XCTSkip("ios/look.mjs names the plan")
        }
        let plan = try JSONDecoder().decode(Plan.self, from: Data(contentsOf: URL(fileURLWithPath: path)))
        continueAfterFailure = true
        for run in plan.runs {
            let app = XCUIApplication()
            app.launchEnvironment = [
                "KNOTLESS_ANSWERS": plan.answers,
                "KNOTLESS_MISSES": plan.misses,
                "KNOTLESS_NOW": plan.now,
                "KNOTLESS_ROUTE": run.route,
                "KNOTLESS_SCHEME": run.scheme,
            ]
            if plan.signedIn { app.launchEnvironment["KNOTLESS_SIGNED_IN"] = "1" }
            if let theme = run.theme { app.launchEnvironment["KNOTLESS_THEME"] = theme }
            // From nothing this simulator's app was last left in, as a picture holds only to what it's asked.
            app.launchEnvironment["KNOTLESS_FRESH"] = "1"
            app.launch()
            sleep(2)
            for step in run.steps { take(step, in: app) }
            sleep(1)
            var last = shot(to: "\(run.out).png")
            guard !run.top else { continue }
            for page in 2...8 {
                app.swipeUp()
                // Till the scroll bar has gone, or the end looks like a move.
                sleep(3)
                let now = XCUIScreen.main.screenshot().pngRepresentation
                if now == last { break }
                try? now.write(to: URL(fileURLWithPath: "\(run.out)-\(page).png"))
                last = now
            }
        }
    }

    private func shot(to path: String) -> Data {
        let png = XCUIScreen.main.screenshot().pngRepresentation
        try? png.write(to: URL(fileURLWithPath: path))
        return png
    }

    private func take(_ step: Step, in app: XCUIApplication) {
        if let label = step.tap, label.hasPrefix("@") {
            let xy = label.dropFirst().split(separator: ",").compactMap { Double($0) }
            app.coordinate(withNormalizedOffset: .zero).withOffset(CGVector(dx: xy[0], dy: xy[1])).tap()
            sleep(2)
        } else if let label = step.tap {
            let match = NSPredicate(format: "label == %@ OR identifier == %@", label, label)
            let found = app.descendants(matching: .any).matching(match).firstMatch
            for _ in 0..<8 where !(found.exists && found.isHittable) { app.swipeUp() }
            guard found.exists, found.isHittable else {
                XCTFail("LOOK: nothing to tap called \(label)")
                return
            }
            found.tap()
            sleep(1)
        }
        if let text = step.type {
            let box = app.textViews.firstMatch.exists ? app.textViews.firstMatch : app.textFields.firstMatch
            guard box.waitForExistence(timeout: 5) else {
                XCTFail("LOOK: nothing to type into")
                return
            }
            box.tap()
            box.typeText(text)
        }
        if step.swipe == "up" { app.swipeUp() }
        if step.swipe == "down" { app.swipeDown() }
        if step.swipe == "left" { app.swipeLeft() }
        if step.swipe == "right" { app.swipeRight() }
    }
}