程序化 SauceLabs 隧道控制
Programatic SauceLabs Tunnel control
有没有人有任何以编程方式控制 SauceLabs SauceConnect 隧道以进行 Selenium WebDriver 测试的经验?具体来自 Java 代码。 SauceLabs 文档中的示例假定隧道是在执行测试之前手动创建的,或者在某处有一个永久隧道供所有测试使用。
每个测试的隧道都需要是唯一的,并且支持在同一个盒子上同时有多个 tunnels/tests 运行。使用该项目的任何人都应该能够使用隧道执行测试,而无需手动步骤来创建隧道或执行隧道软件的特殊安装和配置。
我做了一些研究,想出了一种从代码中控制我的 SauceConnect 隧道的方法。以下是在其他人想要这样做的情况下如何让它工作的简要总结。我正在使用此设置在同一台服务器上 运行 多个隧道,并将每个隧道连接到现场代理服务器。这些示例假设您 运行 在 JVM 上编写代码。
我在博客中整理了一篇更详细的文章 post Controlling Sauce Connect。
依赖关系
将以下依赖项添加到您的项目中。
com.saucelabs.ci-sauce
然后您可以使用下面的代码调用库来启动和停止隧道。
开始隧道
Process tunnel = sauceTunnelManager.openConnection(
sauceUser, // username
sauceKey, // apiKey
port, // port
null, // sauceConnectJar
tunnelOptions, // Tunnel options
null, // printStream
null, // verboseLogging
null // sauceConnectPath );
关闭隧道
sauceTunnelManager.closeTunnelsForPlan(
sauceUser, // username (same as start tunnel)
tunnelOptions, // tunnelOptions (same as start tunnel)
null);
下面是我编写的一系列 java 类 以编程方式控制 SauceTunnel 作为更广泛的解决方案的一部分,该解决方案使用户能够使用 JUnit 与 BrowserStack 或 SauceLabs 对话:
存储库有一个包含 Sauce-Connect.jar.
的 lib 目录
SauceLabsTunnel
package au.com.somecompany.devops.tunnels;
import au.com.somecompany.devops.tunnels.channel.Channel;
import au.com.somecompany.devops.tunnels.channel.SauceLabsChannel;
import au.com.somecompany.devops.utils.Drop;
public class SauceLabsTunnel extends Thread implements Runnable {
private Channel Jar;
private Drop Tunnelled;
private Drop Tested;
public SauceLabsTunnel(Drop tunnelled, Drop tested){
String command = "java -jar lib/Sauce-Connect.jar ";
String successMessage = "Successful handshake with Sauce Connect server";
String failureMessage = "Could not reach Sauce Labs REST API";
String outputTerminal = "Connected! You may start your tests.";
Jar = new SauceLabsChannel(command, System.getProperty("ACCESSKEY"), successMessage, failureMessage, outputTerminal, System.getProperty("USERNAME"));
Tunnelled = tunnelled;
Tested = tested;
}
@Override
public void run() {
Jar.Launch();
while(!Jar.IsJARSuccessfullyLaunched()){
Jar.Launch();
}
Jar.PrintOutput();
Tunnelled.put("TUNNELLED");
for (String message = Tested.take(); !message.equals("TESTED"); message = Tested.take()) {
System.out.println("I see Testing is completed. Closing Tunnel...");
}
Shutdown();
}
public void Shutdown(){
Jar.Shutdown();
}
}
频道
package au.com.somecompany.devops.tunnels.channel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Channel {
protected Process Process;
protected String ChannelCommand = null;
protected String ProcessOutput = null;
protected String SuccessMessage = null;
protected String FailureMessage = null;
protected String OutputTerminal = null;
public Channel(String command, String successMessage, String failureMessage, String outputTerminal){
ChannelCommand = command;
SuccessMessage = successMessage;
FailureMessage = failureMessage;
OutputTerminal = outputTerminal;
}
public void Launch(){
Process = ExecuteJAR();
ProcessOutput = GetOutput(Process.getInputStream());
}
public boolean IsJARSuccessfullyLaunched(){
return ProcessOutput.contains(SuccessMessage) &&
!ProcessOutput.contains(FailureMessage);
}
public void PrintOutput(){
System.out.print(ProcessOutput);
}
public void Shutdown(){
Process.destroy();
}
private String GetOutput(InputStream is){
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuffer sb = new StringBuffer();
try {
String s = br.readLine();
if(s == null) {
return null;
}
while(!s.contains(OutputTerminal)){
sb.append(s).append("\n");
s = br.readLine();
if(s == null || s.contains(OutputTerminal)){
break;
}
}
} catch (IOException e){
e.printStackTrace();
}
return sb.toString();
}
private Process ExecuteJAR() {
Process p;
try {
p = Runtime.getRuntime().exec(ChannelCommand);
} catch (IOException e) {
e.printStackTrace();
return null;
}
catch (Exception e) {
e.printStackTrace();
return null;
}
return p;
}
}
SauceLabsChannel
package au.com.somecompany.devops.tunnels.channel;
public class SauceLabsChannel extends Channel {
public SauceLabsChannel(String command, String accessKey, String successMessage, String failureMessage, String outputTerminal, String username){
super(command, successMessage, failureMessage, outputTerminal);
ChannelCommand += username + " " + accessKey;
}
}
掉落
package au.com.somecompany.devops.utils;
public class Drop {
// Message sent from producer
// to consumer.
private String message;
// True if consumer should wait
// for producer to send message,
// false if producer should wait for
// consumer to retrieve message.
private boolean empty = true;
public synchronized String take() {
// Wait until message is
// available.
while (empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = true;
// Notify producer that
// status has changed.
notifyAll();
return message;
}
public synchronized void put(String message) {
// Wait until message has
// been retrieved.
while (!empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = false;
// Store message.
this.message = message;
// Notify consumer that status
// has changed.
notifyAll();
}
}
有没有人有任何以编程方式控制 SauceLabs SauceConnect 隧道以进行 Selenium WebDriver 测试的经验?具体来自 Java 代码。 SauceLabs 文档中的示例假定隧道是在执行测试之前手动创建的,或者在某处有一个永久隧道供所有测试使用。
每个测试的隧道都需要是唯一的,并且支持在同一个盒子上同时有多个 tunnels/tests 运行。使用该项目的任何人都应该能够使用隧道执行测试,而无需手动步骤来创建隧道或执行隧道软件的特殊安装和配置。
我做了一些研究,想出了一种从代码中控制我的 SauceConnect 隧道的方法。以下是在其他人想要这样做的情况下如何让它工作的简要总结。我正在使用此设置在同一台服务器上 运行 多个隧道,并将每个隧道连接到现场代理服务器。这些示例假设您 运行 在 JVM 上编写代码。
我在博客中整理了一篇更详细的文章 post Controlling Sauce Connect。
依赖关系
将以下依赖项添加到您的项目中。 com.saucelabs.ci-sauce
然后您可以使用下面的代码调用库来启动和停止隧道。
开始隧道
Process tunnel = sauceTunnelManager.openConnection(
sauceUser, // username
sauceKey, // apiKey
port, // port
null, // sauceConnectJar
tunnelOptions, // Tunnel options
null, // printStream
null, // verboseLogging
null // sauceConnectPath );
关闭隧道
sauceTunnelManager.closeTunnelsForPlan(
sauceUser, // username (same as start tunnel)
tunnelOptions, // tunnelOptions (same as start tunnel)
null);
下面是我编写的一系列 java 类 以编程方式控制 SauceTunnel 作为更广泛的解决方案的一部分,该解决方案使用户能够使用 JUnit 与 BrowserStack 或 SauceLabs 对话:
存储库有一个包含 Sauce-Connect.jar.
的 lib 目录SauceLabsTunnel
package au.com.somecompany.devops.tunnels;
import au.com.somecompany.devops.tunnels.channel.Channel;
import au.com.somecompany.devops.tunnels.channel.SauceLabsChannel;
import au.com.somecompany.devops.utils.Drop;
public class SauceLabsTunnel extends Thread implements Runnable {
private Channel Jar;
private Drop Tunnelled;
private Drop Tested;
public SauceLabsTunnel(Drop tunnelled, Drop tested){
String command = "java -jar lib/Sauce-Connect.jar ";
String successMessage = "Successful handshake with Sauce Connect server";
String failureMessage = "Could not reach Sauce Labs REST API";
String outputTerminal = "Connected! You may start your tests.";
Jar = new SauceLabsChannel(command, System.getProperty("ACCESSKEY"), successMessage, failureMessage, outputTerminal, System.getProperty("USERNAME"));
Tunnelled = tunnelled;
Tested = tested;
}
@Override
public void run() {
Jar.Launch();
while(!Jar.IsJARSuccessfullyLaunched()){
Jar.Launch();
}
Jar.PrintOutput();
Tunnelled.put("TUNNELLED");
for (String message = Tested.take(); !message.equals("TESTED"); message = Tested.take()) {
System.out.println("I see Testing is completed. Closing Tunnel...");
}
Shutdown();
}
public void Shutdown(){
Jar.Shutdown();
}
}
频道
package au.com.somecompany.devops.tunnels.channel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Channel {
protected Process Process;
protected String ChannelCommand = null;
protected String ProcessOutput = null;
protected String SuccessMessage = null;
protected String FailureMessage = null;
protected String OutputTerminal = null;
public Channel(String command, String successMessage, String failureMessage, String outputTerminal){
ChannelCommand = command;
SuccessMessage = successMessage;
FailureMessage = failureMessage;
OutputTerminal = outputTerminal;
}
public void Launch(){
Process = ExecuteJAR();
ProcessOutput = GetOutput(Process.getInputStream());
}
public boolean IsJARSuccessfullyLaunched(){
return ProcessOutput.contains(SuccessMessage) &&
!ProcessOutput.contains(FailureMessage);
}
public void PrintOutput(){
System.out.print(ProcessOutput);
}
public void Shutdown(){
Process.destroy();
}
private String GetOutput(InputStream is){
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuffer sb = new StringBuffer();
try {
String s = br.readLine();
if(s == null) {
return null;
}
while(!s.contains(OutputTerminal)){
sb.append(s).append("\n");
s = br.readLine();
if(s == null || s.contains(OutputTerminal)){
break;
}
}
} catch (IOException e){
e.printStackTrace();
}
return sb.toString();
}
private Process ExecuteJAR() {
Process p;
try {
p = Runtime.getRuntime().exec(ChannelCommand);
} catch (IOException e) {
e.printStackTrace();
return null;
}
catch (Exception e) {
e.printStackTrace();
return null;
}
return p;
}
}
SauceLabsChannel
package au.com.somecompany.devops.tunnels.channel;
public class SauceLabsChannel extends Channel {
public SauceLabsChannel(String command, String accessKey, String successMessage, String failureMessage, String outputTerminal, String username){
super(command, successMessage, failureMessage, outputTerminal);
ChannelCommand += username + " " + accessKey;
}
}
掉落
package au.com.somecompany.devops.utils;
public class Drop {
// Message sent from producer
// to consumer.
private String message;
// True if consumer should wait
// for producer to send message,
// false if producer should wait for
// consumer to retrieve message.
private boolean empty = true;
public synchronized String take() {
// Wait until message is
// available.
while (empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = true;
// Notify producer that
// status has changed.
notifyAll();
return message;
}
public synchronized void put(String message) {
// Wait until message has
// been retrieved.
while (!empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = false;
// Store message.
this.message = message;
// Notify consumer that status
// has changed.
notifyAll();
}
}