mobile-dev-inc / maestro

Painless Mobile UI Automation
https://maestro.mobile.dev/
Apache License 2.0
5.83k stars 274 forks source link

A slash in flow name causes Maestro to crash with FileNotFoundException #2017

Open jonathanj opened 1 month ago

jonathanj commented 1 month ago

Is there an existing issue for this?

Steps to reproduce

Create a flow with a name that includes a / (on an operating system that uses / as a path separator, such as macOS), for example:

appId: your.app.id
name: foo / bar
---
- launchApp

Actual results

The flow appears to run, but maestro errors with an exception as soon as the flow finishes:

❌ Error: BlockingCoroutine is cancelling
kotlinx.coroutines.JobCancellationException: BlockingCoroutine is cancelling; job=BlockingCoroutine{Cancelling}@1ef0ac30
Caused by: java.io.FileNotFoundException: /Users/jonathan/.maestro/tests/2024-09-05_085603/ai-report-foo / bar.html (No such file or directory)
    at java.base/java.io.FileOutputStream.open0(Native Method)
    at java.base/java.io.FileOutputStream.open(FileOutputStream.java:293)
    at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:235)
    at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:184)
    at kotlin.io.FilesKt__FileReadWriteKt.writeBytes(FileReadWrite.kt:108)
    at kotlin.io.FilesKt__FileReadWriteKt.writeText(FileReadWrite.kt:134)
    at kotlin.io.FilesKt__FileReadWriteKt.writeText$default(FileReadWrite.kt:134)
    at maestro.cli.report.HtmlAITestSuiteReporter.report(HtmlAITestSuiteReporter.kt:47)
    at maestro.cli.report.TestDebugReporter.saveSuggestions(TestDebugReporter.kt:64)
    at maestro.cli.runner.TestRunner.runSingle(TestRunner.kt:76)
    at maestro.cli.command.TestCommand$handleSessions$1$1$results$1$1$1.invoke(TestCommand.kt:306)
    at maestro.cli.command.TestCommand$handleSessions$1$1$results$1$1$1.invoke(TestCommand.kt:258)
    at maestro.cli.session.MaestroSessionManager.newSession(MaestroSessionManager.kt:102)
    at maestro.cli.session.MaestroSessionManager.newSession$default(MaestroSessionManager.kt:54)
    at maestro.cli.command.TestCommand$handleSessions$1$1$results$1$1.invokeSuspend(TestCommand.kt:258)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:111)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:99)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:585)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:802)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:706)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:693)

The caused by error contains the hint:

Caused by: java.io.FileNotFoundException: /Users/jonathan/.maestro/tests/2024-09-05_085603/ai-report-foo / bar.html 

So it seems to be trying to write to ai-report-${FLOW_NAME}.html which ends up trying to write to a file called bar.html in a directory that doesn't exist.

Expected results

Operations that write to files with user-given input (such as the flow name) should escape/replace characters that have special meaning in filenames, such as the path separator and .. too.

About app

N/A

About environment

Logs

Logs ``` ```

Maestro version

1.38.1

How did you install Maestro?

install script (https://get.maestro.mobile.dev)

Anything else?

No response

bartekpacia commented 1 month ago

Hey, Jonathan, thanks for reporting this bug!

prolificcoder commented 3 weeks ago

I am seeing FileNotFoundExpection in a different context of creating screenrecordings. Its not consistent but if I remove the path to not have / it works good all the time. Full stack trace

java.io.FileNotFoundException: logs/login-smoke-ios-prod.mp4 (No such file or directory)
        at java.base/java.io.FileOutputStream.open0(Native Method)
        at java.base/java.io.FileOutputStream.open(FileOutputStream.java:293)
        at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:235)
        at okio.Okio__JvmOkioKt.sink(JvmOkio.kt:173)
        at okio.Okio.sink(Unknown Source)
        at okio.Okio__JvmOkioKt.sink$default(JvmOkio.kt:173)
        at okio.Okio.sink$default(Unknown Source)
        at maestro.orchestra.Orchestra.startRecordingCommand(Orchestra.kt:745)
        at maestro.orchestra.Orchestra.executeCommand(Orchestra.kt:284)
        at maestro.orchestra.Orchestra.executeCommands(Orchestra.kt:191)
        at maestro.orchestra.Orchestra.runFlow(Orchestra.kt:127)
        at maestro.cli.runner.MaestroCommandRunner.runCommands(MaestroCommandRunner.kt:200)
        at maestro.cli.runner.TestRunner$runSingle$result$1.invoke(TestRunner.kt:61)
        at maestro.cli.runner.TestRunner$runSingle$result$1.invoke(TestRunner.kt:53)
        at maestro.cli.runner.TestRunner.runCatching(TestRunner.kt:155)
        at maestro.cli.runner.TestRunner.runSingle(TestRunner.kt:53)
        at maestro.cli.command.TestCommand$handleSessions$1$1$results$1$1$1.invoke(TestCommand.kt:306)
        at maestro.cli.command.TestCommand$handleSessions$1$1$results$1$1$1.invoke(TestCommand.kt:258)
        at maestro.cli.session.MaestroSessionManager.newSession(MaestroSessionManager.kt:102)
        at maestro.cli.session.MaestroSessionManager.newSession$default(MaestroSessionManager.kt:54)
        at maestro.cli.command.TestCommand$handleSessions$1$1$results$1$1.invokeSuspend(TestCommand.kt:258)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
        at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:111)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:99)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:585)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:802)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:706)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:693)