royaleunit-ant-tasks: <royaleunit player=""/> attribute may be set to chromium, webkit, or firefox to test JS using Playwright

Playwright allows for headless testing of JS in browsers in a more consistent environment than the local user's random installed web browser, including allowing testing with local file paths without security errors/warnings.

If a custom executable is defined for the browser, that still takes precedence over the player attribute, same as before.
diff --git a/royaleunit-ant-tasks/build.xml b/royaleunit-ant-tasks/build.xml
index d7a7105..c33f07c 100644
--- a/royaleunit-ant-tasks/build.xml
+++ b/royaleunit-ant-tasks/build.xml
@@ -47,7 +47,7 @@
   <!-- Options for <javac> tasks -->
   <property name="javac.debug" value="true"/>
   <property name="javac.deprecation" value="false"/>
-  <property name="javac.src" value="1.6"/>
+  <property name="javac.src" value="1.8"/>
 
   <!-- JAR manifest entries -->
   <property name="manifest.sealed" value="false"/>
@@ -112,7 +112,7 @@
         <attribute name="Implementation-Title" value="${manifest.Implementation-Title}"/>
         <attribute name="Implementation-Version" value="${manifest.Implementation-Version}.${build.number}"/>
         <attribute name="Implementation-Vendor" value="${manifest.Implementation-Vendor}"/>
-        <attribute name="Class-Path" value="dom4j.jar java-websocket.jar slf4j-api.jar"/>
+        <attribute name="Class-Path" value="royaleunit/dom4j.jar royaleunit/java-websocket.jar royaleunit/slf4j-api.jar royaleunit/gson.jar royaleunit/driver.jar royaleunit/driver-bundle.jar royaleunit/playwright.jar"/>
       </manifest>
       <fileset dir="${royaleunittasks}/src/main/resources" includes="royaleUnitTasks.tasks"/>
       <fileset dir="${royaleunittasks}/src/main/resources" includes="TestRunner.template"/>
diff --git a/royaleunit-ant-tasks/pom.xml b/royaleunit-ant-tasks/pom.xml
index 0345de7..d810152 100644
--- a/royaleunit-ant-tasks/pom.xml
+++ b/royaleunit-ant-tasks/pom.xml
@@ -39,7 +39,7 @@
           <archive>
             <manifestEntries>
               <!-- These paths are all defined the way the layout will be in the distribution -->
-              <Class-Path>dom4j.jar java-websocket.jar slf4j-api.jar</Class-Path>
+              <Class-Path>royaleunit/dom4j.jar royaleunit/java-websocket.jar royaleunit/slf4j-api.jar royaleunit/playwright.jar royaleunit/driver.jar royaleunit/driver-bundle.jar royaleunit/gson.jar</Class-Path>
             </manifestEntries>
           </archive>
         </configuration>
@@ -78,6 +78,11 @@
       <artifactId>slf4j-simple</artifactId>
       <version>1.7.25</version>
     </dependency>
+    <dependency>
+      <groupId>com.microsoft.playwright</groupId>
+      <artifactId>playwright</artifactId>
+      <version>1.23.0</version>
+    </dependency>
   </dependencies>
 
 <properties /></project>
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/TestRunCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/TestRunCommand.java
new file mode 100644
index 0000000..92b1693
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/TestRunCommand.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.commands;
+
+import java.io.File;
+
+import org.apache.tools.ant.Project;
+
+/**
+ * Class that has its own handle to a {@link Project} and can setup context for
+ * using the project
+ */
+public interface TestRunCommand
+{
+    public void setProject(Project project);
+    public void setEnvironment(String[] variables);
+    public void setUrl(String url);
+    public void setSwf(File swf);
+    public void prepare();
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/TestRunCommandFactory.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/TestRunCommandFactory.java
new file mode 100644
index 0000000..4dc9239
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/TestRunCommandFactory.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.commands;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.royale.test.ant.launcher.OperatingSystem;
+import org.apache.royale.test.ant.launcher.commands.player.AdlCommand;
+import org.apache.royale.test.ant.launcher.commands.player.CustomPlayerCommand;
+import org.apache.royale.test.ant.launcher.commands.player.DefaultPlayerCommand;
+import org.apache.royale.test.ant.launcher.commands.player.FlashPlayerCommand;
+import org.apache.royale.test.ant.launcher.commands.playwright.DefaultPlaywrightCommand;
+import org.apache.royale.test.ant.launcher.commands.playwright.PlaywrightCommand;
+import org.apache.royale.test.ant.launcher.platforms.LinuxDefaults;
+import org.apache.royale.test.ant.launcher.platforms.MacOSXDefaults;
+import org.apache.royale.test.ant.launcher.platforms.WindowsDefaults;
+
+public class TestRunCommandFactory
+{
+    private static final List<String> VALID_PLAYWRIGHT_PLAYERS = Arrays.asList(new String[]{"html", "chromium", "firefox", "webkit"});
+
+    /**
+     * Factory method to create the appropriate player and provide it with a set of defaults for
+     * the executing platform.
+     * 
+     * @param os
+     * @param player  "flash" or "air"
+     * @param customCommand
+     * @param customCommandArgs
+     * @param localTrusted
+     * @return Desired player command with platform defaults possibly wrapped in a custom command
+     */
+    public static TestRunCommand createCommand(OperatingSystem os,
+        String player, File customCommand, String[] customCommandArgs,
+        boolean localTrusted)
+    {
+        TestRunCommand newInstance = null;
+        
+        //choose runtime
+        if (customCommand == null && VALID_PLAYWRIGHT_PLAYERS.contains(player))
+        {
+            PlaywrightCommand playwrightCommand = new DefaultPlaywrightCommand();
+            playwrightCommand.setBrowser(player);
+            newInstance = playwrightCommand;
+        }
+        else
+        {
+            DefaultPlayerCommand defaultInstance = null;
+
+            if (!player.equals("air"))
+            {
+                FlashPlayerCommand fpCommand = new FlashPlayerCommand();
+                fpCommand.setLocalTrusted(localTrusted);
+                defaultInstance = fpCommand;
+            }
+            else
+            {
+                defaultInstance = new AdlCommand();
+            }
+
+            //set defaults
+            if (os.equals(OperatingSystem.WINDOWS))
+            {
+                defaultInstance.setDefaults(new WindowsDefaults());
+            }
+            else if(os.equals(OperatingSystem.MACOSX))
+            {
+                defaultInstance.setDefaults(new MacOSXDefaults());
+            }
+            else
+            {
+                defaultInstance.setDefaults(new LinuxDefaults());
+            }
+
+            //if a custom command has been provided, use it to wrap the default command
+            if(customCommand != null)
+            {
+                CustomPlayerCommand customInstance = new CustomPlayerCommand();
+                customInstance.setProxiedCommand(defaultInstance);
+                customInstance.setExecutable(customCommand);
+                customInstance.setExecutableArgs(customCommandArgs);
+                newInstance = customInstance;
+            }
+            else
+            {
+                newInstance = defaultInstance;
+            }
+        }
+        
+        return newInstance;
+    }
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/CustomPlayerCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/CustomPlayerCommand.java
index ebf6e80..dc2848f 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/CustomPlayerCommand.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/CustomPlayerCommand.java
@@ -20,17 +20,18 @@
 import java.io.IOException;
 import java.util.Vector;
 
+import org.apache.royale.test.ant.LoggingUtil;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.taskdefs.Execute;
-import org.apache.royale.test.ant.LoggingUtil;
 
-public class CustomPlayerCommand implements PlayerCommand
+public class CustomPlayerCommand implements ProcessCommand
 {
     private DefaultPlayerCommand proxiedCommand;
     private File executable;
     private String[] executableArgs;
 
-    public PlayerCommand getProxiedCommand()
+    public ProcessCommand getProxiedCommand()
     {
         return proxiedCommand;
     }
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/DefaultPlayerCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/DefaultPlayerCommand.java
index 92d0a83..2be921c 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/DefaultPlayerCommand.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/DefaultPlayerCommand.java
@@ -20,9 +20,10 @@
 import java.io.IOException;
 
 import org.apache.royale.test.ant.launcher.commands.Command;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
 import org.apache.royale.test.ant.launcher.platforms.PlatformDefaults;
 
-public abstract class DefaultPlayerCommand extends Command implements PlayerCommand
+public abstract class DefaultPlayerCommand extends Command implements ProcessCommand
 {
     private String url;
     private File swf;
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommand.java
index 157c198..55e700f 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommand.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommand.java
@@ -16,23 +16,9 @@
  */
 package org.apache.royale.test.ant.launcher.commands.player;
 
-import java.io.File;
-import java.io.IOException;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
 
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.taskdefs.Execute;
-
-/**
- * Class used to abstract an extension of {@link Execute} that has its own handle to a {@link Project}
- * and can setup context for using the project
- */
-public interface PlayerCommand
+@Deprecated
+public interface PlayerCommand extends ProcessCommand
 {
-    public void setProject(Project project);
-    public void setEnvironment(String[] variables);
-    public File getFileToExecute();
-    public void setSwf(File swf);
-    public void setUrl(String url);
-    public void prepare();
-    public Process launch() throws IOException;
 }
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommandFactory.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommandFactory.java
index 8c3264f..c8857ac 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommandFactory.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/player/PlayerCommandFactory.java
@@ -19,6 +19,7 @@
 import java.io.File;
 
 import org.apache.royale.test.ant.launcher.OperatingSystem;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
 import org.apache.royale.test.ant.launcher.platforms.LinuxDefaults;
 import org.apache.royale.test.ant.launcher.platforms.MacOSXDefaults;
 import org.apache.royale.test.ant.launcher.platforms.WindowsDefaults;
@@ -35,9 +36,9 @@
      * @param localTrusted
      * @return Desired player command with platform defaults possibly wrapped in a custom command
      */
-    public static PlayerCommand createPlayer(OperatingSystem os, String player, File customCommand, String[] customCommandArgs, boolean localTrusted)
+    public static ProcessCommand createPlayer(OperatingSystem os, String player, File customCommand, String[] customCommandArgs, boolean localTrusted)
     {
-        PlayerCommand newInstance = null;
+        ProcessCommand newInstance = null;
 
         DefaultPlayerCommand defaultInstance = null;
         
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/playwright/DefaultPlaywrightCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/playwright/DefaultPlaywrightCommand.java
new file mode 100644
index 0000000..82b862d
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/playwright/DefaultPlaywrightCommand.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.commands.playwright;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+import java.util.function.Consumer;
+
+import org.apache.royale.test.ant.LoggingUtil;
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Execute;
+
+import com.microsoft.playwright.Browser;
+import com.microsoft.playwright.BrowserType;
+import com.microsoft.playwright.ConsoleMessage;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.Playwright;
+import com.microsoft.playwright.Playwright.CreateOptions;
+import com.microsoft.playwright.impl.PlaywrightImpl;
+
+public class DefaultPlaywrightCommand implements PlaywrightCommand
+{
+    private Project project;
+    private String url;
+    private File swf;
+    private String browser;
+    private String[] environment;
+    private Playwright playwright;
+
+    public void setBrowser(String browser)
+    {
+        this.browser = browser;
+    }
+
+    public Project getProject()
+    {
+        return project;
+    }
+
+    public void setProject(Project project)
+    {
+        this.project = project;
+    }
+    
+    public String getUrl()
+    {
+        return url;
+    }
+
+    public void setUrl(String url)
+    {
+        this.url = url;
+    }
+    
+    public File getSwf()
+    {
+        return swf;
+    }
+
+    public void setSwf(File swf)
+    {
+        this.swf = swf;
+    }
+    
+    public void prepare()
+    {
+        ((AntClassLoader)getClass().getClassLoader()).setThreadContextLoader();
+
+        CreateOptions createOptions = new CreateOptions();
+        createOptions.setEnv(getJointEnvironment());
+        playwright = PlaywrightImpl.create(createOptions);
+    }
+    
+    public Playwright launch() throws IOException
+    {
+        LoggingUtil.log("Executing Playwright with " + browser);
+        BrowserType browserType = null;
+        switch (browser)
+        {
+            case "html":
+            case "chromium":
+            {
+                browserType = playwright.chromium();
+                break;
+            }
+            case "webkit":
+            {
+                browserType = playwright.webkit();
+                break;
+            }
+            case "firefox":
+            {
+                browserType = playwright.firefox();
+                break;
+            }
+            default: 
+                throw new IOException("Unknown browser: " + browser);
+        }
+        Browser browserInstance = browserType.launch();
+        Page page = browserInstance.newPage();
+        page.onConsoleMessage(new Consumer<ConsoleMessage>()
+        {
+            @Override
+            public void accept(ConsoleMessage t)
+            {
+                switch (t.type())
+                {
+                    case "error":
+                        LoggingUtil.error(t.text());
+                        break;
+                    default:
+                        LoggingUtil.log(t.text());
+                }
+            } 
+        });
+        
+        if (getUrl() != null)
+        {
+            page.navigate(getUrl());
+        }
+        else
+        {
+            page.navigate(swf.toURI().toString());
+        }
+        return playwright;
+    }
+
+    public void setEnvironment(String[] variables)
+    {
+        environment = variables;
+    }
+
+    /**
+     * Combine process environment variables and command's environment to emulate the default
+     * behavior of the Execute task.  Needed especially when user expects environment to be 
+     * available to custom command (e.g. - xvnc with player not on path).
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String,String> getJointEnvironment()
+    {
+        Map<String, String> jointEnvironment = new HashMap<String, String>();
+        Vector<String> executeProcEnvironment = Execute.getProcEnvironment();
+        String[] procEnvironment = executeProcEnvironment.toArray(new String[0]);
+        for (String envVar : procEnvironment)
+        {
+            String[] parts = envVar.split("=");
+            jointEnvironment.put(parts[0].trim(), parts.length > 1 ? parts[1].trim() : "");
+        }
+        if (environment != null)
+        {
+            for (String envVar : environment)
+            {
+                String[] parts = envVar.split("=");
+                jointEnvironment.put(parts[0].trim(), parts.length > 1 ? parts[1].trim() : "");
+            }
+        }
+        return jointEnvironment;
+    }
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/playwright/PlaywrightCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/playwright/PlaywrightCommand.java
new file mode 100644
index 0000000..d843b71
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/playwright/PlaywrightCommand.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.commands.playwright;
+
+import java.io.IOException;
+
+import org.apache.royale.test.ant.launcher.commands.TestRunCommand;
+import org.apache.tools.ant.Project;
+
+import com.microsoft.playwright.Playwright;
+
+/**
+ * Class used to abstract {@link Playwright} that has its own handle to a {@link Project}
+ * and can setup context for using the project
+ */
+public interface PlaywrightCommand extends TestRunCommand
+{
+    public void setBrowser(String browser);
+    public Playwright launch() throws IOException;
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/process/ProcessCommand.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/process/ProcessCommand.java
new file mode 100644
index 0000000..5f3f2bf
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/commands/process/ProcessCommand.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.commands.process;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.royale.test.ant.launcher.commands.TestRunCommand;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Execute;
+
+/**
+ * Class used to abstract an extension of {@link Execute} that has its own
+ * handle to a {@link Project} and can setup context for using the project
+ */
+public interface ProcessCommand extends TestRunCommand
+{
+    public File getFileToExecute();
+    public Process launch() throws IOException;
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultContext.java
index 412eb35..e5feaba 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultContext.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultContext.java
@@ -16,39 +16,7 @@
  */
 package org.apache.royale.test.ant.launcher.contexts;
 
-import java.io.IOException;
-
-import org.apache.tools.ant.Project;
-import org.apache.royale.test.ant.launcher.commands.player.PlayerCommand;
-
-public class DefaultContext implements ExecutionContext
+@Deprecated
+public class DefaultContext extends DefaultProcessContext
 {
-    private PlayerCommand command;
-    
-    @SuppressWarnings("unused")
-    private Project project;
-    
-    public void setProject(Project project)
-    {
-        this.project = project;
-    }
-    public void setCommand(PlayerCommand command)
-    {
-        this.command = command;
-    }
-
-    public void start() throws IOException
-    {
-        //prep anything the command needs to run
-        command.prepare();
-    }
-
-    public void stop(Process playerProcess) throws IOException
-    {
-        //destroy the process related to the player if it exists
-        if(playerProcess != null)
-        {
-            playerProcess.destroy();
-        }
-    }
 }
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultPlaywrightContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultPlaywrightContext.java
new file mode 100644
index 0000000..0f5ba7d
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultPlaywrightContext.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.contexts;
+
+import java.io.IOException;
+
+import org.apache.royale.test.ant.launcher.commands.playwright.PlaywrightCommand;
+import org.apache.tools.ant.Project;
+
+import com.microsoft.playwright.Playwright;
+
+public class DefaultPlaywrightContext implements PlaywrightExecutionContext
+{
+    private PlaywrightCommand command;
+    
+    @SuppressWarnings("unused")
+    private Project project;
+    
+    public void setProject(Project project)
+    {
+        this.project = project;
+    }
+
+    public void setCommand(PlaywrightCommand command)
+    {
+        this.command = command;
+    }
+
+    public void start() throws IOException
+    {
+        //prep anything the command needs to run
+        command.prepare();
+    }
+
+    public void stop(Playwright playwright) throws IOException
+    {
+        //destroy the playwright instance, if it exists
+        if(playwright != null)
+        {
+			playwright.close();
+        }
+    }
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultProcessContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultProcessContext.java
new file mode 100644
index 0000000..3b0bc80
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/DefaultProcessContext.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.contexts;
+
+import java.io.IOException;
+
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
+import org.apache.tools.ant.Project;
+
+public class DefaultProcessContext implements ProcessExecutionContext
+{
+    private ProcessCommand command;
+    
+    @SuppressWarnings("unused")
+    private Project project;
+    
+    public void setProject(Project project)
+    {
+        this.project = project;
+    }
+
+    public void setCommand(ProcessCommand command)
+    {
+        this.command = command;
+    }
+
+    public void start() throws IOException
+    {
+        //prep anything the command needs to run
+        command.prepare();
+    }
+
+    public void stop(Process process) throws IOException
+    {
+        //destroy the process related to the runtime, if it exists
+        if(process != null)
+        {
+            process.destroy();
+        }
+    }
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContext.java
index 1b5c97d..6af60f0 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContext.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContext.java
@@ -19,29 +19,19 @@
 import java.io.IOException;
 
 import org.apache.tools.ant.Project;
-import org.apache.royale.test.ant.launcher.commands.player.PlayerCommand;
 
 /**
- * Basis for executing a player command.
+ * Basis for executing a test run command.
  */
 public interface ExecutionContext
 {
     public void setProject(Project project);
-    public void setCommand(PlayerCommand command);
     
     /**
-     * Starts the execution context and any work associated with the individual implementations.
+     * Starts the execution context and any work associated with the individual
+     * implementations.
      * 
      * @throws IOException
      */
     public void start() throws IOException;
-    
-    /**
-     * Stops the execution context and manages the player Process as well as any work associated
-     * with the individual implementations.
-     * 
-     * @param playerProcess
-     * @throws IOException
-     */
-    public void stop(Process playerProcess) throws IOException;
 }
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContextFactory.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContextFactory.java
index 89be35d..4cf7c5a 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContextFactory.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ExecutionContextFactory.java
@@ -17,6 +17,9 @@
 package org.apache.royale.test.ant.launcher.contexts;
 
 import org.apache.royale.test.ant.launcher.OperatingSystem;
+import org.apache.royale.test.ant.launcher.commands.TestRunCommand;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
+import org.apache.royale.test.ant.launcher.commands.playwright.PlaywrightCommand;
 
 public class ExecutionContextFactory
 {
@@ -41,9 +44,51 @@
         }
         else
         {
-            context = new DefaultContext();
+            context = new DefaultProcessContext();
         }
         
         return context;
     }
+
+    /**
+     * Used to generate new instances of an execution context based on the test
+     * run command, the OS, and whether the build should run headlessly.
+     * 
+     * @param os Current OS.
+     * @param headless Should the build run headlessly.
+     * @param display The vnc display number to use if headless
+     * @param command The test run command the context is for
+     * 
+     * @return
+     */
+    public static ExecutionContext createContext(TestRunCommand command,
+        OperatingSystem os, boolean headless, int display)
+    {
+        ExecutionContext context = null;
+
+        if (command instanceof PlaywrightCommand)
+        {
+            PlaywrightExecutionContext playwrightContext = new DefaultPlaywrightContext();
+            playwrightContext.setCommand((PlaywrightCommand)command);
+            context = playwrightContext;
+        }
+        else
+        {
+            ProcessExecutionContext processContext = null;
+            boolean trulyHeadless = headless && (os == OperatingSystem.LINUX);
+            if(trulyHeadless)
+            {
+                processContext = new HeadlessContext(display); 
+            }
+            else
+            {
+                processContext = new DefaultProcessContext();
+            }
+            processContext.setCommand((ProcessCommand)command);
+            context = processContext;
+        }
+        
+        
+        return context;
+    }
 }
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/HeadlessContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/HeadlessContext.java
index d18abe3..21fc116 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/HeadlessContext.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/HeadlessContext.java
@@ -23,15 +23,15 @@
 import org.apache.royale.test.ant.launcher.commands.headless.XvncException;
 import org.apache.royale.test.ant.launcher.commands.headless.XvncStartCommand;
 import org.apache.royale.test.ant.launcher.commands.headless.XvncStopCommand;
-import org.apache.royale.test.ant.launcher.commands.player.PlayerCommand;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
 
 /**
  * Context used to wrap a call to the player command in a start and stop of a vncserver.
  * All vncserver commands are blocking.
  */
-public class HeadlessContext implements ExecutionContext
+public class HeadlessContext implements ProcessExecutionContext
 {
-    private PlayerCommand playerCommand;
+    private ProcessCommand playerCommand;
     private int startDisplay;
     private int finalDisplay;
     private Project project;
@@ -46,7 +46,7 @@
         this.project = project;
     }
     
-    public void setCommand(PlayerCommand command)
+    public void setCommand(ProcessCommand command)
     {
         this.playerCommand = command;
     }
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/PlaywrightExecutionContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/PlaywrightExecutionContext.java
new file mode 100644
index 0000000..5063548
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/PlaywrightExecutionContext.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.contexts;
+
+import java.io.IOException;
+
+import org.apache.royale.test.ant.launcher.commands.playwright.PlaywrightCommand;
+
+import com.microsoft.playwright.Playwright;
+
+/**
+ * Basis for executing a Playwright command.
+ */
+public interface PlaywrightExecutionContext extends ExecutionContext
+{
+    public void setCommand(PlaywrightCommand command);
+
+    /**
+     * Stops the execution context and manages the Playwright instnace as well as any work associated
+     * with the individual implementations.
+     * 
+     * @param playwright
+     * @throws IOException
+     */
+    public void stop(Playwright playwright) throws IOException;
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ProcessExecutionContext.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ProcessExecutionContext.java
new file mode 100644
index 0000000..1085146
--- /dev/null
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/launcher/contexts/ProcessExecutionContext.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.royale.test.ant.launcher.contexts;
+
+import java.io.IOException;
+
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
+
+/**
+ * Basis for executing a process command.
+ */
+public interface ProcessExecutionContext extends ExecutionContext
+{
+    public void setCommand(ProcessCommand command);
+
+    /**
+     * Stops the execution context and manages the Process as well as any work
+     * associated with the individual implementations.
+     * 
+     * @param process
+     * @throws IOException
+     */
+    public void stop(Process process) throws IOException;
+}
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/TestRun.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/TestRun.java
index d46e03b..350397a 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/TestRun.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/TestRun.java
@@ -21,20 +21,27 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
+import org.apache.royale.test.ant.IRoyaleUnitServer;
+import org.apache.royale.test.ant.LoggingUtil;
 import org.apache.royale.test.ant.RoyaleUnitSocketServer;
 import org.apache.royale.test.ant.RoyaleUnitSocketThread;
 import org.apache.royale.test.ant.RoyaleUnitWebSocketServer;
-import org.apache.royale.test.ant.IRoyaleUnitServer;
-import org.apache.royale.test.ant.LoggingUtil;
+import org.apache.royale.test.ant.launcher.commands.TestRunCommand;
+import org.apache.royale.test.ant.launcher.commands.TestRunCommandFactory;
 import org.apache.royale.test.ant.launcher.commands.player.AdlCommand;
-import org.apache.royale.test.ant.launcher.commands.player.PlayerCommand;
 import org.apache.royale.test.ant.launcher.commands.player.PlayerCommandFactory;
+import org.apache.royale.test.ant.launcher.commands.playwright.PlaywrightCommand;
+import org.apache.royale.test.ant.launcher.commands.process.ProcessCommand;
 import org.apache.royale.test.ant.launcher.contexts.ExecutionContext;
 import org.apache.royale.test.ant.launcher.contexts.ExecutionContextFactory;
+import org.apache.royale.test.ant.launcher.contexts.PlaywrightExecutionContext;
+import org.apache.royale.test.ant.launcher.contexts.ProcessExecutionContext;
 import org.apache.royale.test.ant.report.Reports;
 import org.apache.royale.test.ant.tasks.configuration.TestRunConfiguration;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+import com.microsoft.playwright.Playwright;
 
 public class TestRun
 {
@@ -58,18 +65,27 @@
 
         try
         {
-            // setup daemon
-            Future<Object> daemon = setupSocketThread();
-
-            // run the execution context and player
-            PlayerCommand player = obtainPlayer();
-            ExecutionContext context = obtainContext(player);
+            // run the execution context and runtime
+            TestRunCommand command = obtainTestRunCommand();
+            ExecutionContext context = obtainContext(command);
             
             //start the execution context
             context.start();
+
+            // setup daemon
+            Future<Object> daemon = setupSocketThread();
         
-            //launch the player
-            Process process = player.launch();
+            //launch the runtime
+            Process process = null;
+            Playwright playwright = null;
+            if (command instanceof ProcessCommand)
+            {
+                process = ((ProcessCommand)command).launch();
+            }
+            else if (command instanceof PlaywrightCommand)
+            {
+                playwright = ((PlaywrightCommand)command).launch();
+            }
 
             try
             {
@@ -79,7 +95,14 @@
             finally
             {
                 //stop the execution context now that socket thread is done
-                context.stop(process);
+                if (context instanceof ProcessExecutionContext)
+                {
+                    ((ProcessExecutionContext)context).stop(process);
+                }
+                else if (context instanceof PlaywrightExecutionContext)
+                {
+                    ((PlaywrightExecutionContext)context).stop(playwright);
+                }
             }
 
             // print summaries and check for failure
@@ -97,10 +120,10 @@
      * 
      * @return PlayerCommand based on user config
      */
-    protected PlayerCommand obtainPlayer()
+    protected ProcessCommand obtainPlayer()
     {
         // get command from factory
-        PlayerCommand command = PlayerCommandFactory.createPlayer(
+        ProcessCommand command = PlayerCommandFactory.createPlayer(
                 configuration.getOs(), 
                 configuration.getPlayer(), 
                 configuration.getCommand(), 
@@ -120,19 +143,46 @@
     }
     
     /**
+     * Fetch the test run command to execute the tests.
+     * 
+     * @return TestRunCommand based on user config
+     */
+    protected TestRunCommand obtainTestRunCommand()
+    {
+        // get command from factory
+        TestRunCommand command = TestRunCommandFactory.createCommand(
+            configuration.getOs(), 
+            configuration.getPlayer(), 
+            configuration.getCommand(), 
+            configuration.getCommandArgs(),
+            configuration.isLocalTrusted());
+        
+        command.setProject(project);
+        command.setSwf(configuration.getSwf());
+        command.setUrl(configuration.getUrl());
+        
+        if(command instanceof AdlCommand) 
+        {
+           ((AdlCommand)command).setPrecompiledAppDescriptor(configuration.getPrecompiledAppDescriptor());
+        }
+        
+        return command;
+    }
+    
+    /**
      * 
      * @param player PlayerCommand which should be executed
      * @return Context to wrap the execution of the PlayerCommand
      */
-    protected ExecutionContext obtainContext(PlayerCommand player)
+    protected ExecutionContext obtainContext(TestRunCommand command)
     {
         ExecutionContext context = ExecutionContextFactory.createContext(
+                command,
                 configuration.getOs(), 
                 configuration.isHeadless(), 
                 configuration.getDisplay());
-        
+
         context.setProject(project);
-        context.setCommand(player);
         
         return context;
     }
@@ -147,7 +197,7 @@
 
         // Create server for use by thread
         IRoyaleUnitServer server = null;
-        if(configuration.getPlayer().equals("html"))
+        if(!"air".equals(configuration.getPlayer()) && !"flash".equals(configuration.getPlayer()))
         {
             server = new RoyaleUnitWebSocketServer(
                 configuration.getPort(), configuration.getSocketTimeout());
diff --git a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/configuration/TaskConfiguration.java b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/configuration/TaskConfiguration.java
index 69edba1..4cae11b 100644
--- a/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/configuration/TaskConfiguration.java
+++ b/royaleunit-ant-tasks/src/main/java/org/apache/royale/test/ant/tasks/configuration/TaskConfiguration.java
@@ -32,7 +32,7 @@
 {
     private final String DEFAULT_WORKING_PATH = ".";
     private final String DEFAULT_REPORT_PATH = ".";
-    private final List<String> VALID_PLAYERS = Arrays.asList(new String[]{"flash", "air", "html"});
+    private final List<String> VALID_PLAYERS = Arrays.asList(new String[]{"flash", "air", "html", "chromium", "firefox", "webkit"});
     
     private String player = "flash";
     private File reportDir = null;
diff --git a/royaleunit-ant-tasks/src/main/resources/downloads.xml b/royaleunit-ant-tasks/src/main/resources/downloads.xml
index 73e8451..44863f2 100644
--- a/royaleunit-ant-tasks/src/main/resources/downloads.xml
+++ b/royaleunit-ant-tasks/src/main/resources/downloads.xml
@@ -80,12 +80,62 @@
       <param name="src.folder" value="dom4j/dom4j/${dom4j.version}"/>
       <param name="src.filename" value="dom4j-${dom4j.version}.jar"/>
       <param name="src.checksum" value="4d8f51d3fe3900efc6e395be48030d6d"/>
-      <param name="dest.folder" value=""/>
+      <param name="dest.folder" value="royaleunit"/>
       <param name="dest.filename" value="${dom4j.name}.jar"/>
       <param name="license.use.url" value="https://raw.githubusercontent.com/dom4j/dom4j/master/LICENSE"/>
       <param name="license.cacheName" value="dom4j-LICENSE.txt"/>
     </antcall>
 
+    <!--  Playwright -->
+    <property name="gson.name" value="gson"/>
+    <property name="gson.version" value="2.9.0"/>
+    <antcall target="download-dependency">
+      <param name="name" value="${gson.name}"/>
+      <param name="src.server" value="${maven.search.url}"/>
+      <param name="src.folder" value="com/google/code/gson/gson/${gson.version}"/>
+      <param name="src.filename" value="gson-${gson.version}.jar"/>
+      <param name="src.checksum" value="53fa3e6753e90d931d62cb89580fde2f"/>
+      <param name="dest.folder" value="royaleunit"/>
+      <param name="dest.filename" value="${gson.name}.jar"/>
+      <param name="license.use.url" value="https://raw.githubusercontent.com/Google/gson/master/LICENSE"/>
+      <param name="license.cacheName" value="${gson.name}-LICENSE.txt"/>
+    </antcall>
+
+    <!--  Playwright -->
+    <property name="playwright.name" value="playwright"/>
+    <property name="playwright.version" value="1.23.0"/>
+    <antcall target="download-dependency">
+      <param name="name" value="${playwright.name}"/>
+      <param name="src.server" value="${maven.search.url}"/>
+      <param name="src.folder" value="com/microsoft/playwright/playwright/${playwright.version}"/>
+      <param name="src.filename" value="playwright-${playwright.version}.jar"/>
+      <param name="src.checksum" value="9e6ef31572b5ac7e21ff7937ce1c1a82"/>
+      <param name="dest.folder" value="royaleunit"/>
+      <param name="dest.filename" value="${playwright.name}.jar"/>
+      <param name="license.use.url" value="https://raw.githubusercontent.com/Microsoft/playwright-java/main/LICENSE"/>
+      <param name="license.cacheName" value="${playwright.name}-LICENSE.txt"/>
+    </antcall>
+    <property name="playwrightdriver.name" value="driver"/>
+    <antcall target="download-dependency">
+      <param name="name" value="${playwrightdriver.name}"/>
+      <param name="src.server" value="${maven.search.url}"/>
+      <param name="src.folder" value="com/microsoft/playwright/driver/${playwright.version}"/>
+      <param name="src.filename" value="driver-${playwright.version}.jar"/>
+      <param name="src.checksum" value="1d433a1eee72d15f96fbffc2b400813c"/>
+      <param name="dest.folder" value="royaleunit"/>
+      <param name="dest.filename" value="${playwrightdriver.name}.jar"/>
+    </antcall>
+    <property name="playwrightdriverbundle.name" value="driver-bundle"/>
+    <antcall target="download-dependency">
+      <param name="name" value="${playwrightdriverbundle.name}"/>
+      <param name="src.server" value="${maven.search.url}"/>
+      <param name="src.folder" value="com/microsoft/playwright/driver-bundle/${playwright.version}"/>
+      <param name="src.filename" value="driver-bundle-${playwright.version}.jar"/>
+      <param name="src.checksum" value="080af38649fb1dca134c3c459bb78eb9"/>
+      <param name="dest.folder" value="royaleunit"/>
+      <param name="dest.filename" value="${playwrightdriverbundle.name}.jar"/>
+    </antcall>
+
     <!--  Java-WebSocket -->
     <property name="javawebsocket.name" value="java-websocket"/>
     <property name="javawebsocket.version" value="1.4.0"/>
@@ -95,7 +145,7 @@
       <param name="src.folder" value="org/java-websocket/Java-WebSocket/${javawebsocket.version}"/>
       <param name="src.filename" value="Java-WebSocket-${javawebsocket.version}.jar"/>
       <param name="src.checksum" value="59c1134b8c50ace9e074e9f1d5da4aaa"/>
-      <param name="dest.folder" value=""/>
+      <param name="dest.folder" value="royaleunit"/>
       <param name="dest.filename" value="${javawebsocket.name}.jar"/>
       <param name="license.use.url" value="https://raw.githubusercontent.com/TooTallNate/Java-WebSocket/master/LICENSE"/>
       <param name="license.cacheName" value="${javawebsocket.name}-LICENSE.txt"/>
@@ -110,7 +160,7 @@
       <param name="src.folder" value="org/slf4j/slf4j-api/${slf4j.version}"/>
       <param name="src.filename" value="slf4j-api-${slf4j.version}.jar"/>
       <param name="src.checksum" value="caafe376afb7086dcbee79f780394ca3"/>
-      <param name="dest.folder" value=""/>
+      <param name="dest.folder" value="royaleunit"/>
       <param name="dest.filename" value="${slf4j.name}.jar"/>
       <param name="license.use.url" value="https://raw.githubusercontent.com/qos-ch/slf4j/master/LICENSE.txt"/>
       <param name="license.cacheName" value="${slf4j.name}-LICENSE.txt"/>