This commit is contained in:
2022-05-17 01:56:44 +03:00
commit 3a9b8c8468
117 changed files with 7175 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
name-template: $NEXT_PATCH_VERSION
tag-template: $NEXT_PATCH_VERSION
template: |
## Whats Changed
$CHANGES
categories:
- title: New features
label: new-feature
- title: Fixes
label: bug
- title: Improvements
label: improvement
+39
View File
@@ -0,0 +1,39 @@
name: build
on:
push:
branches:
- master
jobs:
golang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Golang
uses: actions/setup-go@v2
with:
go-version: 1.17.x
- uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test
run: ci/test.sh
- name: Build
run: ci/build.sh
- name: Latest docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ci/docs.sh latest ${{ secrets.GITHUB_TOKEN }}
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+42
View File
@@ -0,0 +1,42 @@
name: release
on:
release:
types: [published]
jobs:
golang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Golang
uses: actions/setup-go@v2
with:
go-version: 1.17.x
- uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test
run: ci/test.sh
- name: Build
run: ci/build.sh
- name: Prepare release version
run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
- uses: AButler/upload-release-assets@v2.0
with:
files: 'dist/*'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Release docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ci/docs.sh $RELEASE_VERSION ${{ secrets.GITHUB_TOKEN }}
+32
View File
@@ -0,0 +1,32 @@
name: test
on:
pull_request:
jobs:
golang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Golang
uses: actions/setup-go@v2
with:
go-version: 1.17.x
- uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test
run: ci/test.sh
- name: Build
run: ci/build.sh
- uses: codecov/codecov-action@v1
with:
flags: go
+10
View File
@@ -0,0 +1,10 @@
*.iml
.idea
*.deb
*.bak
images
pkged.go
.DS_Store
selenium/barrier/barrier
+389
View File
@@ -0,0 +1,389 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017+ Aerokube Software OÜ
Licensed 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.
+22
View File
@@ -0,0 +1,22 @@
# Browser Images
[![Build Status](https://github.com/aerokube/images/workflows/build/badge.svg)](https://github.com/aerokube/images/actions?query=workflow%3Abuild)
[![Release](https://img.shields.io/github/release/aerokube/images.svg)](https://github.com/aerokube/images/releases/latest)
This repository contains [Docker](http://docker.com/) build files to be used for [Selenoid](http://github.com/aerokube/selenoid) and [Moon](http://github.com/aerokube/moon) projects. You can find prebuilt images [here](https://hub.docker.com/u/selenoid/).
## Download Statistics
### Firefox: [![Firefox Docker Pulls](https://img.shields.io/docker/pulls/selenoid/firefox.svg)](https://hub.docker.com/r/selenoid/firefox)
### Chrome: [![Chrome Docker Pulls](https://img.shields.io/docker/pulls/selenoid/chrome.svg)](https://hub.docker.com/r/selenoid/chrome)
### Opera: [![Opera Docker Pulls](https://img.shields.io/docker/pulls/selenoid/opera.svg)](https://hub.docker.com/r/selenoid/opera)
### Android: [![Android Docker Pulls](https://img.shields.io/docker/pulls/selenoid/android.svg)](https://hub.docker.com/r/selenoid/android)
## Building Images
Moved to: http://aerokube.com/images/latest/#_building_images
## Image information
Moved to: http://aerokube.com/images/latest/#_browser_image_information
+393
View File
@@ -0,0 +1,393 @@
package build
import (
"context"
"errors"
"fmt"
"github.com/markbates/pkger"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
)
const (
LatestVersion = "latest"
)
type Requirements struct {
BrowserSource BrowserSource
BrowserChannel string // "beta", "esr", "dev" and so on
DriverVersion string
Tags []string
NoCache bool
TestsDir string
RunTests bool
IgnoreTests bool
PushImage bool
}
type BrowserSource string
// Return regular file corresponding to this source and optionally download this file
func (bs *BrowserSource) Prepare() (string, string, error) {
src := string(*bs)
if src == "" {
return "", "", errors.New("empty browser source")
}
if _, err := os.Stat(src); err == nil {
pkgName := filepath.Base(src)
return src, extractVersion(pkgName), nil
} else if u, err := url.Parse(src); strings.HasPrefix(src, "http") && err == nil {
pkgName := path.Base(src)
data, err := downloadFile(u.String())
if err != nil {
return "", "", fmt.Errorf("download file: %v", err)
}
f, err := ioutil.TempFile("", "images")
if err != nil {
return "", "", fmt.Errorf("temporary file: %v", err)
}
outputFileName := f.Name()
err = ioutil.WriteFile(outputFileName, data, 0644)
if err != nil {
return "", "", fmt.Errorf("save downloaded file: %v", err)
}
return outputFileName, extractVersion(pkgName), nil
}
return "", src, nil
}
func extractVersion(name string) string {
pieces := strings.Split(name, "_")
version := name
if len(pieces) >= 2 {
version = pieces[1]
}
pieces = strings.Split(version, "+")
pieces = strings.Split(pieces[0], "-")
pieces = strings.Split(pieces[0], "~")
return pieces[0]
}
func versionN(pkgVersion string, n int) string {
buildVersion := pkgVersion
pieces := strings.Split(pkgVersion, ".")
if len(pieces) >= n {
buildVersion = strings.Join(pieces[0:n], ".")
}
return buildVersion
}
func majorVersion(pkgVersion string) string {
return versionN(pkgVersion, 1)
}
func majorMinorVersion(pkgVersion string) string {
return versionN(pkgVersion, 2)
}
func buildVersion(pkgVersion string) string {
return versionN(pkgVersion, 3)
}
type Image struct {
Dir string
BuildArgs []string
Labels []string
FileServer bool
Requirements
}
func NewImage(srcDir string, destDir string, req Requirements) (*Image, error) {
if !requireCommand("docker") {
return nil, fmt.Errorf("docker is not installed")
}
dir, err := copySourceFiles(srcDir, destDir)
if err != nil {
return nil, fmt.Errorf("copy source files: %v", err)
}
if len(req.Tags) == 0 {
return nil, errors.New("image tag is required")
}
return &Image{Dir: dir, Requirements: req}, nil
}
func requireCommand(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
func tmpDir() (string, error) {
dir, err := ioutil.TempDir("", "images")
if err != nil {
return "", fmt.Errorf("create temporary dir: %v", err)
}
return dir, nil
}
func copySourceFiles(srcDir string, destDir string) (string, error) {
const prefix = "/static"
walkDir := filepath.Join(prefix, srcDir)
err := pkger.Walk(walkDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
regex := regexp.MustCompile(`^.+:/static(.+)$`)
relativePath := regex.FindStringSubmatch(path)[1]
outputPath := filepath.Join(destDir, relativePath)
if info.IsDir() {
return os.MkdirAll(outputPath, info.Mode())
}
fileDir := filepath.Join(destDir, filepath.Dir(relativePath))
if !fileExists(fileDir) {
log.Printf("mkdir dir %s", fileDir)
return os.MkdirAll(fileDir, info.Mode())
}
src, err := pkger.Open(path)
if err != nil {
return err
}
defer src.Close()
dest, err := os.Create(outputPath)
if err != nil {
return err
}
defer dest.Close()
_, err = io.Copy(dest, src)
if err != nil {
return err
}
err = dest.Sync()
if err != nil {
return err
}
err = os.Chmod(outputPath, info.Mode())
if err != nil {
return err
}
return nil
})
if err != nil {
return "", err
}
return filepath.Join(destDir, srcDir), nil
}
func (i *Image) Build() error {
args := []string{"build"}
for _, tag := range i.Tags {
args = append(args, "-t", tag)
}
if len(i.BuildArgs) > 0 {
for _, arg := range i.BuildArgs {
args = append(args, "--build-arg", arg)
}
}
if httpProxy := os.Getenv("HTTP_PROXY"); httpProxy != "" {
args = append(args, "--build-arg", fmt.Sprintf("http_proxy=%s", httpProxy))
}
if httpsProxy := os.Getenv("HTTPS_PROXY"); httpsProxy != "" {
args = append(args, "--build-arg", fmt.Sprintf("https_proxy=%s", httpsProxy))
}
if len(i.Labels) > 0 {
for _, label := range i.Labels {
args = append(args, "--label", label)
}
}
if i.NoCache {
args = append(args, "--no-cache")
}
if i.FileServer {
server := &http.Server{
Handler: http.FileServer(http.Dir(i.Dir)),
}
ln, err := net.Listen("tcp", ":8080")
if err != nil {
return fmt.Errorf("failed to allocate free port: %v", err)
}
e := make(chan error)
go func() {
e <- server.Serve(ln)
}()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
defer server.Shutdown(ctx)
if runtime.GOOS == "linux" {
ip, err := dockerHostIP()
if err != nil {
return fmt.Errorf("failed to detect host machine IP: %v", err)
}
args = append(args, "--add-host", fmt.Sprintf("host.docker.internal:%s", ip))
}
}
args = append(args, i.Dir)
log.Printf("running command: docker %s", strings.Join(args, " "))
cmd := exec.Command("docker", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
return fmt.Errorf("failed to start command: %v", err)
}
err = cmd.Wait()
if err != nil {
return fmt.Errorf("command execution error: %v", err)
}
return nil
}
func dockerHostIP() (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range ifaces {
if iface.Name != "docker0" {
continue
}
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
return ip.String(), nil
}
}
return "", errors.New("no live network interfaces detected")
}
func (i *Image) Test(testsDir string, browserName string, browserVersion string) error {
if !i.RunTests {
log.Println("not running tests")
return nil
}
ref := i.Tags[0]
err := doTest(ref, testsDir, browserName, browserVersion)
if err != nil {
if i.IgnoreTests {
log.Printf("ignoring tests: %v", err)
return nil
}
return fmt.Errorf("tests error: %v", err)
}
log.Println("tests passed")
return nil
}
func doTest(ref string, testsDir string, browserName string, browserVersion string) error {
if !fileExists(testsDir) {
return fmt.Errorf("tests directory %s does not exist", testsDir)
}
seleniumUrl := "http://localhost:4445/"
if browserName == "firefox" || (browserName == "opera" && browserVersion == "12.16") {
seleniumUrl = "http://localhost:4445/wd/hub"
}
exec.Command("docker", "rm", "-f", "selenium").Output()
defer func() {
exec.Command("docker", "rm", "-f", "selenium").Output()
}()
output, err := exec.Command("docker", "run", "-d", "--name", "selenium", "--privileged", "-p", "4445:4444", ref).Output()
if err != nil {
return fmt.Errorf("failed to start docker image: %s %v", string(output), err)
}
if !requireCommand("mvn") {
return fmt.Errorf("maven is not installed")
}
mvnCmd := exec.Command("mvn", "clean", "test",
fmt.Sprintf("-Dgrid.connection.url=%s", seleniumUrl),
fmt.Sprintf("-Dgrid.browser.name=%s", browserName),
fmt.Sprintf("-Dgrid.browser.version=%s", browserVersion),
)
mvnCmd.Dir = testsDir
mvnCmd.Stdout = os.Stdout
mvnCmd.Stderr = os.Stderr
err = mvnCmd.Start()
if err != nil {
return fmt.Errorf("failed to start tests: %v", err)
}
err = mvnCmd.Wait()
if err != nil {
return fmt.Errorf("tests finished with error: %v", err)
}
return nil
}
func fileExists(p string) bool {
_, err := os.Stat(p)
return !os.IsNotExist(err)
}
func (i *Image) Push() error {
if i.PushImage {
for _, tag := range i.Tags {
cmd := exec.Command("docker", "push", tag)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
return fmt.Errorf("invalid docker push %s: %v", tag, err)
}
err = cmd.Wait()
if err != nil {
return fmt.Errorf("pushing failed: %v", err)
}
}
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
package build
import (
. "github.com/aandryashin/matchers"
"testing"
)
func TestVersionFromPackageName(t *testing.T) {
AssertThat(t, extractVersion("firefox_45.0.2+build1-0ubuntu0.14.04.1+aerokube0_amd64.deb"), EqualTo{"45.0.2"})
AssertThat(t, extractVersion("firefox_71.0_b11+build1-0ubuntu0.18.04.1_amd64.deb"), EqualTo{"71.0"})
AssertThat(t, extractVersion("firefox-trunk_72.0_a1_hg20191114r501767-0ubuntu0.18.04.1_umd1_amd64.deb"), EqualTo{"72.0"})
AssertThat(t, extractVersion("google-chrome-stable_48.0.2564.109-1+aerokube0_amd64.deb"), EqualTo{"48.0.2564.109"})
AssertThat(t, extractVersion("google-chrome-beta_79.0.3945.36-1_amd64.deb"), EqualTo{"79.0.3945.36"})
AssertThat(t, extractVersion("google-chrome-unstable_80.0.3964.0-1_amd64.deb"), EqualTo{"80.0.3964.0"})
AssertThat(t, extractVersion("opera-stable_38.0.2220.31_amd64.deb"), EqualTo{"38.0.2220.31"})
AssertThat(t, extractVersion("opera-beta_65.0.3467.32_amd64.deb"), EqualTo{"65.0.3467.32"})
AssertThat(t, extractVersion("opera-developer_66.0.3502.0_amd64.deb"), EqualTo{"66.0.3502.0"})
AssertThat(t, extractVersion("yandex-browser-beta_19.9.3.358-1_amd64.deb"), EqualTo{"19.9.3.358"})
}
func TestVersionToTag(t *testing.T) {
AssertThat(t, extractVersion("45.0.2+build1-0ubuntu0.14.04.1+aerokube0"), EqualTo{"45.0.2"})
AssertThat(t, extractVersion("48.0.2564.109-1+aerokube0"), EqualTo{"48.0.2564.109"})
AssertThat(t, extractVersion("78.0.3904.108-1"), EqualTo{"78.0.3904.108"})
AssertThat(t, extractVersion("38.0.2220.31"), EqualTo{"38.0.2220.31"})
AssertThat(t, extractVersion("71.0~b11+build1-0ubuntu0.18.04.1"), EqualTo{"71.0"})
AssertThat(t, extractVersion("74.0~a1~hg20200205r512485-0ubuntu0.18.04.1~umd1"), EqualTo{"74.0"})
}
func TestParseVersion(t *testing.T) {
AssertThat(t, majorVersion("78.0.3904.108"), EqualTo{"78"})
AssertThat(t, majorVersion("78"), EqualTo{"78"})
AssertThat(t, majorMinorVersion("78.0.3904.108"), EqualTo{"78.0"})
AssertThat(t, majorMinorVersion("78.0"), EqualTo{"78.0"})
AssertThat(t, buildVersion("78.0.3904.108"), EqualTo{"78.0.3904"})
AssertThat(t, buildVersion("78.0.3904"), EqualTo{"78.0.3904"})
}
+178
View File
@@ -0,0 +1,178 @@
package build
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
)
const (
chromeDriverBinary = "chromedriver"
)
type Chrome struct {
Requirements
}
func (c *Chrome) Build() error {
pkgSrcPath, pkgVersion, err := c.BrowserSource.Prepare()
if err != nil {
return fmt.Errorf("invalid browser source: %v", err)
}
pkgTagVersion := extractVersion(pkgVersion)
driverVersion, err := c.parseChromeDriverVersion(pkgTagVersion)
if err != nil {
return fmt.Errorf("parse chromedriver version: %v", err)
}
// Build dev image
devDestDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create dev temporary dir: %v", err)
}
srcDir := "chrome/apt"
if pkgSrcPath != "" {
srcDir = "chrome/local"
pkgDestDir := filepath.Join(devDestDir, srcDir)
err := os.MkdirAll(pkgDestDir, 0755)
if err != nil {
return fmt.Errorf("create %v temporary dir: %v", pkgDestDir, err)
}
pkgDestPath := filepath.Join(pkgDestDir, "google-chrome.deb")
err = os.Rename(pkgSrcPath, pkgDestPath)
if err != nil {
return fmt.Errorf("move package: %v", err)
}
}
devImageTag := fmt.Sprintf("selenoid/dev_chrome:%s", pkgTagVersion)
devImageRequirements := Requirements{NoCache: c.NoCache, Tags: []string{devImageTag}}
devImage, err := NewImage(srcDir, devDestDir, devImageRequirements)
if err != nil {
return fmt.Errorf("init dev image: %v", err)
}
devBuildArgs := []string{fmt.Sprintf("VERSION=%s", pkgVersion)}
devBuildArgs = append(devBuildArgs, c.channelToBuildArgs()...)
devImage.BuildArgs = devBuildArgs
if pkgSrcPath != "" {
devImage.FileServer = true
}
err = devImage.Build()
if err != nil {
return fmt.Errorf("build dev image: %v", err)
}
// Build main image
destDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create temporary dir: %v", err)
}
image, err := NewImage("chrome", destDir, c.Requirements)
if err != nil {
return fmt.Errorf("init image: %v", err)
}
image.BuildArgs = append(image.BuildArgs, fmt.Sprintf("VERSION=%s", pkgTagVersion))
err = c.downloadChromeDriver(image.Dir, driverVersion)
if err != nil {
return fmt.Errorf("failed to download chromedriver: %v", err)
}
image.Labels = []string{fmt.Sprintf("driver=chromedriver:%s", driverVersion)}
err = image.Build()
if err != nil {
return fmt.Errorf("build image: %v", err)
}
err = image.Test(c.TestsDir, "chrome", pkgTagVersion)
if err != nil {
return fmt.Errorf("test image: %v", err)
}
err = image.Push()
if err != nil {
return fmt.Errorf("push image: %v", err)
}
return nil
}
func (c *Chrome) channelToBuildArgs() []string {
switch c.BrowserChannel {
case "beta":
return []string{"PACKAGE=google-chrome-beta", "INSTALL_DIR=chrome-beta"}
case "dev":
return []string{"PACKAGE=google-chrome-unstable", "INSTALL_DIR=chrome-unstable"}
default:
return []string{}
}
}
func (c *Chrome) parseChromeDriverVersion(pkgVersion string) (string, error) {
version := c.DriverVersion
if version == LatestVersion {
const baseUrl = "https://chromedriver.storage.googleapis.com/"
v, err := c.getLatestChromeDriver(baseUrl, pkgVersion)
if err != nil {
return "", err
}
version = v
}
return version, nil
}
func (c *Chrome) downloadChromeDriver(dir string, version string) error {
u := fmt.Sprintf("http://chromedriver.storage.googleapis.com/%s/chromedriver_linux64.zip", version)
_, err := downloadDriver(u, chromeDriverBinary, dir)
if err != nil {
return fmt.Errorf("download chromedriver: %v", err)
}
return nil
}
func (c *Chrome) getLatestChromeDriver(baseUrl string, pkgVersion string) (string, error) {
fetchVersion := func(url string) (string, error) {
data, err := sendGet(url)
if err != nil {
return "", fmt.Errorf("read chromedriver version: %v", err)
}
return string(data), nil
}
if c.BrowserChannel != "dev" {
chromeBuildVersion := buildVersion(pkgVersion)
u := baseUrl + fmt.Sprintf("LATEST_RELEASE_%s", chromeBuildVersion)
v, err := fetchVersion(u)
if err == nil {
return v, nil
}
}
chromeMajorVersion, err := strconv.Atoi(majorVersion(pkgVersion))
if err != nil {
return "", fmt.Errorf("chrome major version: %v", err)
}
u := baseUrl + fmt.Sprintf("LATEST_RELEASE_%d", chromeMajorVersion)
v, err := fetchVersion(u)
if err == nil {
return v, nil
} else {
previousChromeMajorVersion := chromeMajorVersion - 1
u = baseUrl + fmt.Sprintf("LATEST_RELEASE_%d", previousChromeMajorVersion)
v, err := fetchVersion(u)
if err == nil {
return v, nil
} else {
return "", errors.New("could not find compatible chromedriver")
}
}
}
+122
View File
@@ -0,0 +1,122 @@
package build
import (
"fmt"
"os"
"path/filepath"
)
const (
msedgeDriverBinary = "msedgedriver"
)
type Edge struct {
Requirements
}
func (c *Edge) Build() error {
pkgSrcPath, pkgVersion, err := c.BrowserSource.Prepare()
if err != nil {
return fmt.Errorf("invalid browser source: %v", err)
}
pkgTagVersion := extractVersion(pkgVersion)
// Build dev image
devDestDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create dev temporary dir: %v", err)
}
srcDir := "edge/apt"
if pkgSrcPath != "" {
srcDir = "edge/local"
pkgDestDir := filepath.Join(devDestDir, srcDir)
err := os.MkdirAll(pkgDestDir, 0755)
if err != nil {
return fmt.Errorf("create %v temporary dir: %v", pkgDestDir, err)
}
pkgDestPath := filepath.Join(pkgDestDir, "microsoft-edge.deb")
err = os.Rename(pkgSrcPath, pkgDestPath)
if err != nil {
return fmt.Errorf("move package: %v", err)
}
}
devImageTag := fmt.Sprintf("selenoid/dev_edge:%s", pkgTagVersion)
devImageRequirements := Requirements{NoCache: c.NoCache, Tags: []string{devImageTag}}
devImage, err := NewImage(srcDir, devDestDir, devImageRequirements)
if err != nil {
return fmt.Errorf("init dev image: %v", err)
}
devBuildArgs := []string{fmt.Sprintf("VERSION=%s", pkgVersion)}
devBuildArgs = append(devBuildArgs, c.channelToBuildArgs()...)
devImage.BuildArgs = devBuildArgs
if pkgSrcPath != "" {
devImage.FileServer = true
}
err = devImage.Build()
if err != nil {
return fmt.Errorf("build dev image: %v", err)
}
// Build main image
destDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create temporary dir: %v", err)
}
image, err := NewImage("edge", destDir, c.Requirements)
if err != nil {
return fmt.Errorf("init image: %v", err)
}
image.BuildArgs = append(image.BuildArgs, fmt.Sprintf("VERSION=%s", pkgTagVersion))
driverVersion, err := c.downloadMSEdgeDriver(image.Dir)
if err != nil {
return fmt.Errorf("failed to download msedgedriver: %v", err)
}
image.Labels = []string{fmt.Sprintf("driver=msedgedriver:%s", driverVersion)}
err = image.Build()
if err != nil {
return fmt.Errorf("build image: %v", err)
}
err = image.Test(c.TestsDir, "MicrosoftEdge", pkgTagVersion)
if err != nil {
return fmt.Errorf("test image: %v", err)
}
err = image.Push()
if err != nil {
return fmt.Errorf("push image: %v", err)
}
return nil
}
func (c *Edge) channelToBuildArgs() []string {
switch c.BrowserChannel {
case "beta":
return []string{"PACKAGE=microsoft-edge-beta", "INSTALL_DIR=msedge-beta"}
case "dev":
return []string{"PACKAGE=microsoft-edge-dev", "INSTALL_DIR=msedge-dev"}
default:
return []string{}
}
}
func (c *Edge) downloadMSEdgeDriver(dir string) (string, error) {
version := c.DriverVersion
// Full driver versions list can be fetched as XML from https://msedgedriver.azureedge.net/
u := fmt.Sprintf("https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/%s/edgedriver_linux64.zip", version)
_, err := downloadDriver(u, msedgeDriverBinary, dir)
if err != nil {
return "", fmt.Errorf("download msedgedriver: %v", err)
}
return version, nil
}
+326
View File
@@ -0,0 +1,326 @@
package build
import (
"archive/tar"
"archive/zip"
"bufio"
"bytes"
"compress/gzip"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"gopkg.in/cheggaaa/pb.v1"
)
const (
zipMagicHeader = "504b"
gzipMagicHeader = "1f8b"
)
func downloadFile(url string) ([]byte, error) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
err := downloadFileWithProgressBar(url, w)
if err != nil {
return nil, err
}
w.Flush()
return b.Bytes(), nil
}
func downloadFileWithProgressBar(url string, w io.Writer) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("file download error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("unexpected response code: %d", resp.StatusCode)
}
contentLength := int(resp.ContentLength)
writer := w
if contentLength > 0 {
bar := pb.New(contentLength).SetUnits(pb.U_BYTES)
bar.Output = os.Stderr
bar.Start()
defer bar.Finish()
writer = io.MultiWriter(w, bar)
}
_, err = io.Copy(writer, resp.Body)
if err != nil {
return fmt.Errorf("failed to save file: %v", err)
}
return nil
}
func downloadDriver(url string, filename string, outputDir string) (string, error) {
log.Printf("downloading driver from %s", url)
data, err := downloadFile(url)
if err != nil {
return "", fmt.Errorf("failed to download driver archive: %v", err)
}
return extractFile(data, filename, outputDir)
}
func getMagicHeader(data []byte) string {
if len(data) >= 2 {
return hex.EncodeToString(data[:2])
}
return ""
}
func isZipFile(data []byte) bool {
return getMagicHeader(data) == zipMagicHeader
}
func isTarGzFile(data []byte) bool {
return getMagicHeader(data) == gzipMagicHeader
}
func extractFile(data []byte, filename string, outputDir string) (string, error) {
if isZipFile(data) {
return unzip(data, filename, outputDir)
} else if isTarGzFile(data) {
return untar(data, filename, outputDir)
} else {
outputPath := filepath.Join(outputDir, filename)
err := ioutil.WriteFile(outputPath, data, os.ModePerm)
if err != nil {
return "", fmt.Errorf("failed to save file %s: %v", outputPath, err)
}
fmt.Println("Patched chromedriver")
patchDriver(outputPath)
return outputPath, nil
}
}
func patchDriver(filename string) {
rand.Seed(time.Now().UnixNano())
var lettersLower = []byte("abcdefghijklmnopqrstuvwxyz")
var lettersUpper = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
rand_str_repl := make([]byte, 26)
for i := range rand_str_repl {
if i == 2 {
rand_str_repl[i] = rand_str_repl[0]
} else if i == 3 {
rand_str_repl[i] = byte('_')
} else if i == 20 || i == 21 {
rand_str_repl[i] = lettersUpper[rand.Intn(len(lettersUpper))]
} else {
rand_str_repl[i] = lettersLower[rand.Intn(len(lettersLower))]
}
}
log.Println(string(rand_str_repl))
re := regexp.MustCompile(`cdc_.{22}`)
input_file, err := ioutil.ReadFile(filename)
log.Println(filename)
if err == nil {
match := re.Find(input_file)
if match == nil {
log.Fatalln("cant find driver cdc_")
}
log.Println(string(match))
output_data := re.ReplaceAll(input_file, rand_str_repl)
match = re.Find(output_data)
if match != nil {
log.Fatalln("cant patch driver cdc_")
}
err := ioutil.WriteFile(filename, output_data, os.ModePerm)
if err != nil {
log.Fatalln("cant write to file")
}
}
}
// Based on http://stackoverflow.com/questions/20357223/easy-way-to-unzip-file-with-golang
func unzip(data []byte, fileName string, outputDir string) (string, error) {
zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
// Closure to address file descriptors issue with all the deferred .Close() methods
extractAndWriteFile := func(f *zip.File) (string, error) {
rc, err := f.Open()
if err != nil {
return "", err
}
defer rc.Close()
outputPath := filepath.Join(outputDir, f.Name)
if f.FileInfo().IsDir() {
return "", fmt.Errorf("can only unzip files but %s is a directory", f.Name)
}
err = outputFile(outputPath, f.Mode(), rc)
if err != nil {
return "", err
}
fmt.Println("Patched chromedriver")
patchDriver(outputPath)
return outputPath, nil
}
if err == nil {
for _, f := range zr.File {
if f.Name == fileName {
return extractAndWriteFile(f)
}
}
err = fmt.Errorf("file %s does not exist in archive", fileName)
}
return "", err
}
// Based on https://medium.com/@skdomino/taring-untaring-files-in-go-6b07cf56bc07
func untar(data []byte, fileName string, outputDir string) (string, error) {
gzr, err := gzip.NewReader(bytes.NewReader(data))
defer gzr.Close()
extractAndWriteFile := func(tr *tar.Reader, header *tar.Header) (string, error) {
outputPath := filepath.Join(outputDir, header.Name)
if header.Typeflag == tar.TypeDir {
return "", fmt.Errorf("can only untar files but %s is a directory", header.Name)
}
err = outputFile(outputPath, os.FileMode(header.Mode), tr)
if err != nil {
return "", err
}
return outputPath, nil
}
if err == nil {
tr := tar.NewReader(gzr)
loop:
for {
header, err := tr.Next()
switch {
case err == io.EOF:
break loop
case err != nil:
return "", err
case header == nil:
continue
}
return extractAndWriteFile(tr, header)
}
err = fmt.Errorf("file %s does not exist in archive", fileName)
}
return "", err
}
func outputFile(outputPath string, mode os.FileMode, r io.Reader) error {
os.MkdirAll(filepath.Dir(outputPath), 0755)
f, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
if err != nil {
return err
}
return nil
}
func doSendGet(url string, token string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("invalid request: %v", err)
}
if token != "" {
req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unsuccessful response: %d %s", resp.StatusCode, resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response body: %v", err)
}
return data, nil
}
func sendGet(url string) ([]byte, error) {
return doSendGet(url, "")
}
func sendGetWithAuth(url string, token string) ([]byte, error) {
return doSendGet(url, token)
}
func latestGithubRelease(repo string) (string, error) {
token := os.Getenv("GITHUB_TOKEN")
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo)
data, err := sendGetWithAuth(url, token)
if err != nil {
return "", fmt.Errorf("get latest github release data: %v", err)
}
type info struct {
TagName string `json:"tag_name"`
}
var i info
err = json.Unmarshal(data, &i)
if err != nil {
return "", fmt.Errorf("json unmarshal: %v", err)
}
return i.TagName, nil
}
func latestGithubLinuxAsset(repo string) (string, error) {
token := os.Getenv("GITHUB_TOKEN")
url := fmt.Sprintf("https://api.github.com/repos/%s/releases", repo)
data, err := sendGetWithAuth(url, token)
if err != nil {
return "", fmt.Errorf("get github releases data: %v", err)
}
type AssetInfo struct {
Name string `json:"name"`
}
type Release struct {
Assets []AssetInfo `json:"assets"`
}
type Releases []Release
var releases Releases
err = json.Unmarshal(data, &releases)
if err != nil {
return "", fmt.Errorf("json unmarshal: %v", err)
}
for _, release := range releases {
for _, asset := range release.Assets {
if strings.Contains(asset.Name, "linux") {
return asset.Name, nil
}
}
}
return "", fmt.Errorf("could not find github linux asset")
}
+65
View File
@@ -0,0 +1,65 @@
package build
import (
. "github.com/aandryashin/matchers"
"io/ioutil"
"os"
"testing"
)
func TestUnzip(t *testing.T) {
data := readFile(t, "testfile.zip")
AssertThat(t, isZipFile(data), Is{true})
AssertThat(t, isTarGzFile(data), Is{false})
testUnpack(t, data, "zip-testfile", func(data []byte, filePath string, outputDir string) (string, error) {
return unzip(data, filePath, outputDir)
}, "zip\n")
}
func TestUntar(t *testing.T) {
data := readFile(t, "testfile.tar.gz")
AssertThat(t, isTarGzFile(data), Is{true})
AssertThat(t, isZipFile(data), Is{false})
testUnpack(t, data, "gzip-testfile", func(data []byte, filePath string, outputDir string) (string, error) {
return untar(data, filePath, outputDir)
}, "gzip\n")
}
func withTmpDir(t *testing.T, prefix string, fn func(*testing.T, string)) {
dir, err := ioutil.TempDir("", prefix)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
fn(t, dir)
}
func testUnpack(t *testing.T, data []byte, fileName string, fn func([]byte, string, string) (string, error), correctContents string) {
withTmpDir(t, "test-unpack", func(t *testing.T, dir string) {
unpackedFile, err := fn(data, fileName, dir)
if err != nil {
t.Fatal(err)
}
if !fileExists(unpackedFile) {
t.Fatalf("file %s does not exist\n", unpackedFile)
}
unpackedFileContents := string(readFile(t, unpackedFile))
if unpackedFileContents != correctContents {
t.Fatalf("incorrect unpacked file contents; expected: '%s', actual: '%s'\n", correctContents, unpackedFileContents)
}
})
}
func readFile(t *testing.T, fileName string) []byte {
data, err := ioutil.ReadFile(fileName)
if err != nil {
t.Fatal(err)
}
return data
}
+228
View File
@@ -0,0 +1,228 @@
package build
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
const (
geckoDriverBinary = "geckodriver"
)
type Firefox struct {
SelenoidVersion string
SeleniumVersion string
Requirements
}
func (c *Firefox) Build() error {
if c.SelenoidVersion == "" && c.SeleniumVersion == "" {
return errors.New("missing Selenoid or Selenium JAR version")
}
// Build dev image
devDestDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create dev temporary dir: %v", err)
}
devSrcDir := "firefox/apt"
pkgSrcPath, pkgVersion, err := c.BrowserSource.Prepare()
if err != nil {
return fmt.Errorf("invalid browser source: %v", err)
}
if pkgSrcPath != "" {
devSrcDir = "firefox/local"
pkgDestDir := filepath.Join(devDestDir, devSrcDir)
err := os.MkdirAll(pkgDestDir, 0755)
if err != nil {
return fmt.Errorf("create %v temporary dir: %v", pkgDestDir, err)
}
pkgDestPath := filepath.Join(pkgDestDir, "firefox.deb")
err = os.Rename(pkgSrcPath, pkgDestPath)
if err != nil {
return fmt.Errorf("move package: %v", err)
}
}
pkgTagVersion := extractVersion(pkgVersion)
devImageTag := fmt.Sprintf("selenoid/dev_firefox:%s", pkgTagVersion)
devImageRequirements := Requirements{NoCache: c.NoCache, Tags: []string{devImageTag}}
devImage, err := NewImage(devSrcDir, devDestDir, devImageRequirements)
if err != nil {
return fmt.Errorf("init dev image: %v", err)
}
devBuildArgs := []string{fmt.Sprintf("VERSION=%s", pkgVersion)}
devBuildArgs = append(devBuildArgs, c.channelToBuildArgs()...)
devImage.BuildArgs = devBuildArgs
if pkgSrcPath != "" {
devImage.FileServer = true
}
err = devImage.Build()
if err != nil {
return fmt.Errorf("build dev image: %v", err)
}
// Build main image
destDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create temporary dir: %v", err)
}
firefoxMajorVersion, err := strconv.Atoi(majorVersion(pkgTagVersion))
geckoDriverCompatible := firefoxMajorVersion > 48
srcDir := "firefox/selenoid"
if !geckoDriverCompatible {
srcDir = "firefox/selenium"
}
image, err := NewImage(srcDir, destDir, c.Requirements)
if err != nil {
return fmt.Errorf("init dev image: %v", err)
}
image.BuildArgs = append(image.BuildArgs, fmt.Sprintf("VERSION=%s", pkgTagVersion))
firefoxMajorMinorVersion := majorMinorVersion(pkgTagVersion)
if geckoDriverCompatible {
driverVersion, err := c.downloadGeckoDriver(image.Dir)
if err != nil {
return fmt.Errorf("failed to download geckodriver: %v", err)
}
labels := []string{fmt.Sprintf("driver=geckodriver:%s", driverVersion)}
selenoidVersion, err := c.downloadSelenoid(image.Dir)
if err != nil {
return fmt.Errorf("failed to download Selenoid: %v", err)
}
labels = append(labels, fmt.Sprintf("selenoid=%s", selenoidVersion))
image.Labels = labels
browsersJsonFile := filepath.Join(image.Dir, "browsers.json")
data, err := ioutil.ReadFile(browsersJsonFile)
if err != nil {
return fmt.Errorf("failed to read browsers.json: %v", err)
}
newContents := strings.Replace(string(data), "@@VERSION@@", firefoxMajorMinorVersion, -1)
err = ioutil.WriteFile(browsersJsonFile, []byte(newContents), 0)
if err != nil {
return fmt.Errorf("failed to update browsers.json: %v", err)
}
} else {
driverVersion, err := c.downloadSeleniumJAR(image.Dir)
if err != nil {
return fmt.Errorf("failed to download Selenium JAR: %v", err)
}
image.Labels = []string{fmt.Sprintf("driver=selenium:%s", driverVersion)}
}
err = image.Build()
if err != nil {
return fmt.Errorf("build image: %v", err)
}
err = image.Test(c.TestsDir, "firefox", firefoxMajorMinorVersion)
if err != nil {
return fmt.Errorf("test image: %v", err)
}
err = image.Push()
if err != nil {
return fmt.Errorf("push image: %v", err)
}
return nil
}
func (c *Firefox) channelToBuildArgs() []string {
switch c.BrowserChannel {
case "beta":
return []string{"PPA=ppa:mozillateam/firefox-next"}
case "dev":
return []string{"PACKAGE=firefox-trunk", "PPA=ppa:ubuntu-mozilla-daily/ppa"}
case "esr":
return []string{"PACKAGE=firefox-esr", "PPA=ppa:mozillateam/ppa"}
default:
return []string{}
}
}
func (c *Firefox) downloadGeckoDriver(dir string) (string, error) {
version := c.DriverVersion
if version == LatestVersion {
v, err := latestGithubRelease("mozilla/geckodriver")
if err != nil {
return "", fmt.Errorf("latest geckodriver version: %v", err)
}
version = strings.TrimPrefix(v, "v")
}
u := fmt.Sprintf("https://github.com/mozilla/geckodriver/releases/download/v%s/geckodriver-v%s-linux64.tar.gz", version, version)
_, err := downloadDriver(u, geckoDriverBinary, dir)
if err != nil {
return "", fmt.Errorf("download geckodriver: %v", err)
}
return version, nil
}
func (c *Firefox) downloadSelenoid(dir string) (string, error) {
version := c.SelenoidVersion
if version == LatestVersion {
v, err := latestGithubRelease("aerokube/selenoid")
if err != nil {
return "", fmt.Errorf("latest Selenoid version: %v", err)
}
version = v
}
u := fmt.Sprintf("https://github.com/aerokube/selenoid/releases/download/%s/selenoid_linux_amd64", version)
data, err := downloadFile(u)
if err != nil {
return "", fmt.Errorf("download Selenoid: %v", err)
}
outputPath := filepath.Join(dir, "selenoid")
err = ioutil.WriteFile(outputPath, data, 0755)
if err != nil {
return "", fmt.Errorf("save Selenoid: %v", err)
}
return version, nil
}
func (c *Firefox) downloadSeleniumJAR(dir string) (string, error) {
version := c.SeleniumVersion
var u string
switch version {
case "2.15.0", "2.19.0", "2.20.0", "2.21.0", "2.25.0", "2.32.0", "2.35.0", "2.37.0", "2.39.0", "2.40.0", "2.41.0", "2.43.1", "2.44.0", "2.45.0", "2.48.2":
u = fmt.Sprintf("https://repo.jenkins-ci.org/releases/org/seleniumhq/selenium/selenium-server-standalone/%s/selenium-server-standalone-$selenium_version.jar", version)
case "2.47.1":
u = "http://selenium-release.storage.googleapis.com/2.47/selenium-server-standalone-2.47.1.jar"
case "2.53.1":
u = "http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar"
case "3.2.0":
u = "http://selenium-release.storage.googleapis.com/3.2/selenium-server-standalone-3.2.0.jar"
case "3.3.1":
u = "http://selenium-release.storage.googleapis.com/3.3/selenium-server-standalone-3.3.1.jar"
case "3.4.0":
u = "https://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar"
default:
return "", fmt.Errorf("unsupported Selenium JAR version: %s", version)
}
data, err := downloadFile(u)
if err != nil {
return "", fmt.Errorf("download Selenium JAR: %v", err)
}
outputPath := filepath.Join(dir, "selenium-server-standalone.jar")
err = ioutil.WriteFile(outputPath, data, 0644)
if err != nil {
return "", fmt.Errorf("save Selenium JAR: %v", err)
}
return version, nil
}
+128
View File
@@ -0,0 +1,128 @@
package build
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const (
operaDriverBinary = "operadriver_linux64/operadriver"
)
type Opera struct {
Requirements
}
func (o *Opera) Build() error {
// Build dev image
devDestDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create dev temporary dir: %v", err)
}
srcDir := "opera/apt"
pkgSrcPath, pkgVersion, err := o.BrowserSource.Prepare()
if err != nil {
return fmt.Errorf("invalid browser source: %v", err)
}
if pkgSrcPath != "" {
srcDir = "opera/local"
pkgDestDir := filepath.Join(devDestDir, srcDir)
err := os.MkdirAll(pkgDestDir, 0755)
if err != nil {
return fmt.Errorf("create %v temporary dir: %v", pkgDestDir, err)
}
pkgDestPath := filepath.Join(pkgDestDir, "opera.deb")
err = os.Rename(pkgSrcPath, pkgDestPath)
if err != nil {
return fmt.Errorf("move package: %v", err)
}
}
pkgTagVersion := extractVersion(pkgVersion)
devImageTag := fmt.Sprintf("selenoid/dev_opera:%s", pkgTagVersion)
devImageRequirements := Requirements{NoCache: o.NoCache, Tags: []string{devImageTag}}
devImage, err := NewImage(srcDir, devDestDir, devImageRequirements)
if err != nil {
return fmt.Errorf("init dev image: %v", err)
}
devBuildArgs := []string{fmt.Sprintf("VERSION=%s", pkgVersion)}
devBuildArgs = append(devBuildArgs, o.channelToBuildArgs()...)
devImage.BuildArgs = devBuildArgs
if pkgSrcPath != "" {
devImage.FileServer = true
}
err = devImage.Build()
if err != nil {
return fmt.Errorf("build dev image: %v", err)
}
// Build main image
destDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create temporary dir: %v", err)
}
image, err := NewImage("opera", destDir, o.Requirements)
if err != nil {
return fmt.Errorf("init image: %v", err)
}
image.BuildArgs = append(image.BuildArgs, fmt.Sprintf("VERSION=%s", pkgTagVersion))
driverVersion, err := o.downloadOperaDriver(image.Dir)
if err != nil {
return fmt.Errorf("failed to download operadriver: %v", err)
}
image.Labels = []string{fmt.Sprintf("driver=operadriver:%s", driverVersion)}
err = image.Build()
if err != nil {
return fmt.Errorf("build image: %v", err)
}
err = image.Test(o.TestsDir, "opera", pkgTagVersion)
if err != nil {
return fmt.Errorf("test image: %v", err)
}
err = image.Push()
if err != nil {
return fmt.Errorf("push image: %v", err)
}
return nil
}
func (o *Opera) channelToBuildArgs() []string {
switch o.BrowserChannel {
case "beta":
return []string{"PACKAGE=opera-beta"}
case "dev":
return []string{"PACKAGE=opera-developer"}
default:
return []string{}
}
}
func (o *Opera) downloadOperaDriver(dir string) (string, error) {
version := o.DriverVersion
if version == LatestVersion {
v, err := latestGithubRelease("operasoftware/operachromiumdriver")
if err != nil {
return "", fmt.Errorf("latest Operadriver version: %v", err)
}
version = strings.TrimPrefix(v, "v.")
}
u := fmt.Sprintf("https://github.com/operasoftware/operachromiumdriver/releases/download/v.%s/operadriver_linux64.zip", version)
_, err := downloadDriver(u, operaDriverBinary, dir)
if err != nil {
return "", fmt.Errorf("download Operadriver: %v", err)
}
return version, nil
}
Binary file not shown.
Binary file not shown.
+117
View File
@@ -0,0 +1,117 @@
package build
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const (
yandexDriverBinary = "yandexdriver"
)
type YandexBrowser struct {
Requirements
}
func (yb *YandexBrowser) Build() error {
// Build dev image
devDestDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create dev temporary dir: %v", err)
}
srcDir := "yandex/apt"
pkgSrcPath, pkgVersion, err := yb.BrowserSource.Prepare()
if err != nil {
return fmt.Errorf("invalid browser source: %v", err)
}
if pkgSrcPath != "" {
srcDir = "yandex/local"
pkgDestDir := filepath.Join(devDestDir, srcDir)
err := os.MkdirAll(pkgDestDir, 0755)
if err != nil {
return fmt.Errorf("create %v temporary dir: %v", pkgDestDir, err)
}
pkgDestPath := filepath.Join(pkgDestDir, "yandex-browser.deb")
err = os.Rename(pkgSrcPath, pkgDestPath)
if err != nil {
return fmt.Errorf("move package: %v", err)
}
}
pkgTagVersion := extractVersion(pkgVersion)
devImageTag := fmt.Sprintf("selenoid/dev_yandex:%s", pkgTagVersion)
devImageRequirements := Requirements{NoCache: yb.NoCache, Tags: []string{devImageTag}}
devImage, err := NewImage(srcDir, devDestDir, devImageRequirements)
if err != nil {
return fmt.Errorf("init dev image: %v", err)
}
devBuildArgs := []string{fmt.Sprintf("VERSION=%s", pkgVersion)}
devImage.BuildArgs = devBuildArgs
if pkgSrcPath != "" {
devImage.FileServer = true
}
err = devImage.Build()
if err != nil {
return fmt.Errorf("build dev image: %v", err)
}
// Build main image
destDir, err := tmpDir()
if err != nil {
return fmt.Errorf("create temporary dir: %v", err)
}
image, err := NewImage("yandex", destDir, yb.Requirements)
if err != nil {
return fmt.Errorf("init image: %v", err)
}
image.BuildArgs = append(image.BuildArgs, fmt.Sprintf("VERSION=%s", pkgTagVersion))
driverVersion, err := yb.downloadYandexDriver(image.Dir)
if err != nil {
return fmt.Errorf("failed to download yandexdriver: %v", err)
}
image.Labels = []string{fmt.Sprintf("driver=yandexdriver:%s", driverVersion)}
err = image.Build()
if err != nil {
return fmt.Errorf("build image: %v", err)
}
err = image.Test(yb.TestsDir, "yandex", pkgTagVersion)
if err != nil {
return fmt.Errorf("test image: %v", err)
}
err = image.Push()
if err != nil {
return fmt.Errorf("push image: %v", err)
}
return nil
}
func (yb *YandexBrowser) downloadYandexDriver(dir string) (string, error) {
version := yb.DriverVersion
if version == LatestVersion {
a, err := latestGithubLinuxAsset("yandex/YandexDriver")
if err != nil {
return "", fmt.Errorf("latest yandexdriver linux asset: %v", err)
}
version = strings.Split(a, "-")[1]
}
bv := buildVersion(version)
u := fmt.Sprintf("https://github.com/yandex/YandexDriver/releases/download/v%s-stable/yandexdriver-%s-linux.zip", bv, version)
_, err := downloadDriver(u, yandexDriverBinary, dir)
if err != nil {
return "", fmt.Errorf("download Yandexdriver: %v", err)
}
return version, nil
}
Executable
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
export GO111MODULE="on"
go get github.com/markbates/pkger/cmd/pkger
go get -u github.com/mitchellh/gox # cross compile
go generate github.com/aerokube/images
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X github.com/aerokube/images/cmd.buildStamp=$(date -u '+%Y-%m-%d_%I:%M:%S%p') -X github.com/aerokube/images/cmd.gitRevision=$(git describe --tags || git rev-parse HEAD) -s -w"
gox -os "linux darwin windows" -arch "amd64" -osarch="windows/386" -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X github.com/aerokube/images/cmd.buildStamp=$(date -u '+%Y-%m-%d_%I:%M:%S%p') -X github.com/aerokube/images/cmd.gitRevision=$(git describe --tags || git rev-parse HEAD) -s -w"
Executable
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -e
TAGNAME=$1
GH_REF=github.com/aerokube/images.git
git config user.name "${GITHUB_REPOSITORY}"
git config user.email "aerokube@aerokube.github.com"
git remote add upstream "https://${GITHUB_TOKEN}@${GH_REF}"
git fetch upstream
git branch -r
echo "Deleting old output"
rm -rf ${GITHUB_WORKSPACE}/docs/output
mkdir ${GITHUB_WORKSPACE}/docs/output
git worktree prune
rm -rf ${GITHUB_WORKSPACE}/.git/worktrees/docs/output/
echo "Checking out gh-pages branch into docs/output"
git worktree add -B gh-pages ${GITHUB_WORKSPACE}/docs/output upstream/gh-pages
echo "Removing existing files"
mkdir -p ${GITHUB_WORKSPACE}/docs/output/${TAGNAME}
rm -rf ${GITHUB_WORKSPACE}/docs/output/${TAGNAME}/*
echo "Copying images"
cp -R ${GITHUB_WORKSPACE}/docs/img ${GITHUB_WORKSPACE}/docs/output/${TAGNAME}/img
echo "Generating docs"
docker run -v ${GITHUB_WORKSPACE}/docs/:/documents/ --name asciidoc-to-html asciidoctor/docker-asciidoctor asciidoctor -a revnumber=${TAGNAME} -D /documents/output/${TAGNAME} index.adoc
echo "Updating gh-pages branch"
cd ${GITHUB_WORKSPACE}/docs/output && git add --all && git commit -m "Publishing to gh-pages"
git push upstream HEAD:gh-pages
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
export GO111MODULE="on"
go test -race -v -coverprofile=coverage.txt -covermode=atomic ./...
+28
View File
@@ -0,0 +1,28 @@
package cmd
import (
"github.com/aerokube/images/build"
"github.com/spf13/cobra"
)
var (
chromeCmd = &cobra.Command{
Use: "chrome",
Short: "build Chrome image",
RunE: func(cmd *cobra.Command, args []string) error {
req := build.Requirements{
BrowserSource: build.BrowserSource(browserSource),
BrowserChannel: browserChannel,
DriverVersion: driverVersion,
NoCache: noCache,
TestsDir: testsDir,
RunTests: test,
IgnoreTests: ignoreTests,
Tags: tags,
PushImage: push,
}
chrome := &build.Chrome{req}
return chrome.Build()
},
}
)
+28
View File
@@ -0,0 +1,28 @@
package cmd
import (
"github.com/aerokube/images/build"
"github.com/spf13/cobra"
)
var (
edgeCmd = &cobra.Command{
Use: "edge",
Short: "build Microsoft Edge image",
RunE: func(cmd *cobra.Command, args []string) error {
req := build.Requirements{
BrowserSource: build.BrowserSource(browserSource),
BrowserChannel: browserChannel,
DriverVersion: driverVersion,
NoCache: noCache,
TestsDir: testsDir,
RunTests: test,
IgnoreTests: ignoreTests,
Tags: tags,
PushImage: push,
}
edge := &build.Edge{req}
return edge.Build()
},
}
)
+36
View File
@@ -0,0 +1,36 @@
package cmd
import (
"github.com/aerokube/images/build"
"github.com/spf13/cobra"
)
var (
selenoidVersion string
seleniumVersion string
firefoxCmd = &cobra.Command{
Use: "firefox",
Short: "build Firefox image",
RunE: func(cmd *cobra.Command, args []string) error {
req := build.Requirements{
BrowserSource: build.BrowserSource(browserSource),
BrowserChannel: browserChannel,
DriverVersion: driverVersion,
NoCache: noCache,
TestsDir: testsDir,
RunTests: test,
IgnoreTests: ignoreTests,
Tags: tags,
PushImage: push,
}
firefox := &build.Firefox{SelenoidVersion: selenoidVersion, SeleniumVersion: seleniumVersion, Requirements: req}
return firefox.Build()
},
}
)
func init() {
firefoxCmd.Flags().StringVar(&selenoidVersion, "selenoid-version", build.LatestVersion, "Selenoid binary version")
firefoxCmd.Flags().StringVar(&seleniumVersion, "selenium-version", "", "Selenium JAR version")
}
+28
View File
@@ -0,0 +1,28 @@
package cmd
import (
"github.com/aerokube/images/build"
"github.com/spf13/cobra"
)
var (
operaCmd = &cobra.Command{
Use: "opera",
Short: "build Opera image",
RunE: func(cmd *cobra.Command, args []string) error {
req := build.Requirements{
BrowserSource: build.BrowserSource(browserSource),
BrowserChannel: browserChannel,
DriverVersion: driverVersion,
NoCache: noCache,
TestsDir: testsDir,
RunTests: test,
IgnoreTests: ignoreTests,
Tags: tags,
PushImage: push,
}
opera := &build.Opera{req}
return opera.Build()
},
}
)
+65
View File
@@ -0,0 +1,65 @@
package cmd
import (
"github.com/aerokube/images/build"
"github.com/spf13/cobra"
"log"
"os"
"path/filepath"
)
var (
browserSource string
browserChannel string
driverVersion string
noCache bool
testsDir string
test bool
ignoreTests bool
push bool
tags []string
rootCmd = &cobra.Command{
Use: "images",
Short: "images is a tool for building Docker images with browsers",
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Usage()
},
}
)
func initFlags() {
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("get current dir: %v", err)
}
defaultTestsDir := filepath.Join(cwd, "../selenoid-container-tests")
rootCmd.PersistentFlags().StringSliceVarP(&tags, "tag", "t", []string{}, "image tag")
rootCmd.PersistentFlags().StringVarP(&browserSource, "browser", "b", "", "browser APT package version, package file path, package file URL")
rootCmd.PersistentFlags().StringVarP(&driverVersion, "driver-version", "d", build.LatestVersion, "webdriver version")
rootCmd.PersistentFlags().StringVarP(&browserChannel, "channel", "c", "default", "browser channel")
rootCmd.PersistentFlags().BoolVarP(&noCache, "no-cache", "n", false, "do not use Docker cache")
rootCmd.PersistentFlags().StringVar(&testsDir, "tests-dir", defaultTestsDir, "directory with tests")
rootCmd.PersistentFlags().BoolVar(&test, "test", false, "run tests")
rootCmd.PersistentFlags().BoolVar(&ignoreTests, "ignore-tests", false, "continue to run even if tests failed")
rootCmd.PersistentFlags().BoolVarP(&push, "push", "p", false, "push image to Docker registry")
}
func init() {
initFlags()
rootCmd.AddCommand(chromeCmd)
rootCmd.AddCommand(edgeCmd)
rootCmd.AddCommand(firefoxCmd)
rootCmd.AddCommand(operaCmd)
rootCmd.AddCommand(yandexCmd)
rootCmd.AddCommand(versionCmd)
}
func Execute() {
if _, err := rootCmd.ExecuteC(); err != nil {
log.Fatalf("command error: %v", err)
}
}
+22
View File
@@ -0,0 +1,22 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var (
gitRevision = "HEAD"
buildStamp = "unknown"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Show version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Git Revision: %s\n", gitRevision)
fmt.Printf("UTC Build Time: %s\n", buildStamp)
os.Exit(0)
},
}
+28
View File
@@ -0,0 +1,28 @@
package cmd
import (
"github.com/aerokube/images/build"
"github.com/spf13/cobra"
)
var (
yandexCmd = &cobra.Command{
Use: "yandex",
Short: "build Yandex.Browser image",
RunE: func(cmd *cobra.Command, args []string) error {
req := build.Requirements{
BrowserSource: build.BrowserSource(browserSource),
BrowserChannel: browserChannel,
DriverVersion: driverVersion,
NoCache: noCache,
TestsDir: testsDir,
RunTests: test,
IgnoreTests: ignoreTests,
Tags: tags,
PushImage: push,
}
yandexBrowser := &build.YandexBrowser{req}
return yandexBrowser.Build()
},
}
)
+44
View File
@@ -0,0 +1,44 @@
== Android
WARNING: Hardware server or virtual machine with nested virtualization support is required to run Android images.
.Android Images
|===
| Image | Android version | Appium version
| browsers/android:4.4 | 4.4 | 1.18.1
| browsers/android:5.0 | 5.0 | 1.18.1
| browsers/android:5.1 | 5.1 | 1.18.1
| browsers/android:6.0 | 6.0 | 1.18.1
| browsers/android:7.0 | 7.0 | 1.18.1
| browsers/android:7.1 | 7.1 | 1.18.1
| browsers/android:8.0 | 8.0 | 1.18.1
| browsers/android:8.1 | 8.1 | 1.18.1
| browsers/android:9.0 | 9.0 | 1.18.1
| browsers/android:10.0 | 10.0 | 1.18.1
|===
[NOTE]
====
. These images include VNC server and Android Quick Boot snapshot.
. Neither Chromedriver nor Chrome Mobile are installed. To test hybrid apps build your own image using provided automation script.
====
An example `browsers.json` for Android images looks like the following:
[source,json]
----
{
"android": {
"default": "6.0",
"versions": {
"6.0": {
"image": "browsers/android:6.0",
"port": "4444",
"path": "/wd/hub"
}
}
}
}
----
An example Java test can be found https://github.com/aerokube/demo-tests/blob/master/src/test/java/com/aerokube/selenoid/AndroidRemoteApkTest.java[here].
+156
View File
@@ -0,0 +1,156 @@
== Building Images
=== What's Inside Images
Each image consists of 3 layers:
. **Base layer** - contains stuff needed in every image: Xvfb, fonts, locales, cursor blinking fix, timezone definition and so on. You have to build this layer manually.
. **Browser layer** - contains browser binary.
. **Driver layer** - contains either respective web driver binary or corresponding Selenium \ Selenoid server version.
=== Building Procedure
Building procedure is automated with a Go binary. All Docker build files are embedded to this binary. To build the binary from source:
[source,bash]
----
$ go get github.com/markbates/pkger/cmd/pkger
$ go generate github.com/aerokube/images
$ go build
----
To show help:
[source,bash]
----
$ ./images --help
$ ./images firefox --help
----
* Before building images you can optionally clone tests repository:
+
[source,bash]
----
$ git clone https://github.com/aerokube/selenoid-container-tests.git
----
+
These tests require Java and Maven 3 to be installed. Tests directory should be cloned to this repository parent directory:
+
[source,bash]
----
images/ # <== this repo
selenoid-container-tests/ # <== optional tests repo
----
* To build a Firefox image use the following command:
+
[source,bash]
----
$ ./images firefox -b 79.0+build1-0ubuntu0.18.04.1 -d 0.26.0 --selenoid-version 1.10.0 -t selenoid/firefox:79.0
----
+
Here `79.0+build1-0ubuntu0.18.04.1` is `firefox` package version for Ubuntu 18.04, `1.10.10` is https://github.com/aerokube/selenoid/releases[Selenoid] version to use inside image (just use latest release version here), `selenoid/firefox:79.0` is Docker tag to be applied, `0.26.0` is http://github.com/mozilla/geckodriver/releases[Geckodriver] version to use.
+
If you wish to automatically use the latest Selenoid and Geckodriver versions - just omit respective flags or use **latest** as value:
+
[source,bash]
----
$ ./images firefox -b 79.0+build1-0ubuntu0.18.04.1 -t selenoid/firefox:79.0
----
+
If you wish to pack a local Debian package instead of APT - just replace package version with full path to **deb** file:
+
[source,bash]
----
$ ./images firefox -b /path/to/firefox_79.0+build1-0ubuntu0.18.04.1_amd64.deb -t selenoid/firefox:79.0
----
+
It is important to use package files with full version specified name because automation scripts determine browser version by parsing package file name!
+
To run the tests after building the image add `--test` flag:
+
[source,bash]
----
$ ./images firefox -b 79.0+build1-0ubuntu0.18.04.1 -t selenoid/firefox:79.0 --test
----
+
To push image after building add `--push` flag:
+
[source,bash]
----
$ ./images firefox -b 79.0+build1-0ubuntu0.18.04.1 -t selenoid/firefox:79.0 --push
----
* To build a Chrome image use the following command:
+
[source,bash]
----
$ ./images chrome -b 78.0.3904.97-1 -d 78.0.3904.70 -t selenoid/chrome:78.0
----
+
Here `78.0.3904.97-1` is `google-chrome-stable` package version for Ubuntu 18.04, `78.0.3904.70` is https://chromedriver.storage.googleapis.com/index.html[Chromedriver] version, `selenoid/chrome:78.0` is Docker tag to be applied.
+
If you wish to automatically use the latest https://chromedriver.chromium.org/downloads/version-selection[compatible] Chromedriver version - just omit respective flag or use **latest** as value:
+
[source,bash]
----
$ ./images chrome -b 78.0.3904.97-1 -d latest -t selenoid/chrome:78.0
----
* To build a Microsoft Edge image use the following command:
+
[source,bash]
----
$ ./images edge -b 88.0.673.0-1 -d 88.0.673.0 -t selenoid/edge:88.0
----
* To build an Opera image use the following command:
+
[source,bash]
----
$ ./images opera -b 64.0.3417.92 -d 77.0.3865.120 -t selenoid/opera:64.0
----
+
Here `64.0.3417.92` is `opera-stable` package version for Ubuntu 18.04, `77.0.3865.120` is https://github.com/operasoftware/operachromiumdriver/releases[Operadriver] version, `64.0` is Docker tag to be applied.
* To build a Yandex image use the following command:
+
[source,bash]
----
$ ./images yandex -b 20.4.3.268-1 -d 20.4.3.321 -t selenoid/yandex-browser:20.4
----
+
Here `20.4.3.268-1` is `yandex-browser-beta` package version for Ubuntu 18.04, `20.4.3.321` is https://github.com/yandex/YandexDriver/releases[Yandexdriver] Linux asset version, `20.4` is Docker tag to be applied.
* To build an Android image use the following command:
+
[source,bash]
----
$ cd selenium
$ ./automate_android.sh
----
This command is interactive - just answer the questions and it will build an image for you. In order to bundle custom APK to image - put it to `selenium/android` directory before running the script.
=== Selecting Browser Channel
Apart from the default stable release channel, the following ones are also supported:
.Available Browser Channels
|===
| Browser | Channel | Package |
| firefox | beta | firefox (http://launchpad.net/~mozillateam/+archive/firefox-next/+packages[PPA]) |
| firefox | dev | firefox-trunk (http://launchpad.net/~ubuntu-mozilla-daily/+archive/ppa/+packages[PPA]) |
| firefox | esr | firefox-esr (http://launchpad.net/~mozillateam/+archive/ppa/+packages[PPA]) |
| chrome | beta | google-chrome-beta |
| chrome | dev | google-chrome-unstable |
| opera | beta | opera-beta |
| opera | dev | opera-developer |
|===
To build an image for one of the channels above use the `--channel` flag as follows:
[source,bash]
----
$ ./images firefox -b 72.0~a1~hg20191114r501767-0ubuntu0.18.04.1~umd1 --channel dev -t selenoid/firefox:72.0a1
----
+16
View File
@@ -0,0 +1,16 @@
== Chrome Developer Tools Protocol
These images should be used with tools and frameworks relying on https://chromedevtools.github.io/devtools-protocol/[Chrome Developer Tools Protocol] such as https://ppts.dev[Puppeteer].
.Chrome Developer Tools Protocol Images
|===
| Image | Chrome version
| cdtp/chrome:85.0 | 85.0.4183.121
| cdtp/chrome:86.0 | 86.0.4240.111
| cdtp/chrome:87.0 | 87.0.4280.66
| cdtp/chrome:88.0 | 88.0.4324.182
| cdtp/chrome:89.0 | 89.0.4389.72
| cdtp/chrome:90.0 | 90.0.4430.85
| cdtp/chrome:93.0 | 93.0.4577.63
|===
+40
View File
@@ -0,0 +1,40 @@
== Chrome Mobile
WARNING: Hardware server or virtual machine with nested virtualization support is required to run Chrome Mobile images.
.Chrome Mobile Images
|===
| Image | Android version | Appium version | Chromedriver version | Chrome version
| selenoid/chrome-mobile:73.0 | 9.0 | 1.18.1 | 73.0.3683.68 | 73.0.3683.90
| selenoid/chrome-mobile:74.0 | 9.0 | 1.18.1 | 74.0.3729.6 | 74.0.3729.157
| selenoid/chrome-mobile:75.0 | 9.0 | 1.18.1 | 75.0.3770.8 | 75.0.3770.143
| selenoid/chrome-mobile:76.0 | 9.0 | 1.18.1 | 76.0.3809.126 | 76.0.3809.132
| selenoid/chrome-mobile:77.0 | 9.0 | 1.18.1 | 77.0.3865.40 | 77.0.3865.116
| selenoid/chrome-mobile:78.0 | 9.0 | 1.18.1 | 78.0.3904.105 | 78.0.3904.96
| selenoid/chrome-mobile:79.0 | 9.0 | 1.18.1 | 79.0.3945.36 | 79.0.3945.136
| selenoid/chrome-mobile:80.0 | 9.0 | 1.18.1 | 80.0.3987.106 | 80.0.3987.162
| selenoid/chrome-mobile:81.0 | 9.0 | 1.18.1 | 81.0.4044.69 | 81.0.4044.138
| - | - | - | - | 82.0.x.x (release skipped by development team)
| selenoid/chrome-mobile:83.0 | 9.0 | 1.18.1 | 83.0.4103.39 | 83.0.4103.106
| selenoid/chrome-mobile:84.0 | 9.0 | 1.18.1 | 84.0.4147.30 | 84.0.4147.125
| selenoid/chrome-mobile:85.0 | 9.0 | 1.18.1 | 85.0.4183.87 | 85.0.4183.127
| selenoid/chrome-mobile:86.0 | 9.0 | 1.18.1 | 86.0.4240.22 | 86.0.4240.110
|===
An example `browsers.json` for Chrome Mobile images looks like the following:
[source,json]
----
{
"chrome": {
"default": "mobile-75.0",
"versions": {
"mobile-75.0": {
"image": "selenoid/chrome-mobile:75.0",
"port": "4444",
"path": "/wd/hub"
}
}
}
}
----
+67
View File
@@ -0,0 +1,67 @@
== Chrome
.Chrome Images
|===
| Image | VNC Image | Chromedriver version | Chrome version
| selenoid/chrome:48.0 | selenoid/vnc_chrome:48.0 | 2.21 | 48.0.2564.116
| selenoid/chrome:49.0 | selenoid/vnc_chrome:49.0 | 2.22 | 49.0.2623.112
| selenoid/chrome:50.0 | selenoid/vnc_chrome:50.0 | 2.22 | 50.0.2661.102
| selenoid/chrome:51.0 | selenoid/vnc_chrome:51.0 | 2.23 | 51.0.2704.106
| selenoid/chrome:52.0 | selenoid/vnc_chrome:52.0 | 2.24 | 52.0.2743.116
| selenoid/chrome:53.0 | selenoid/vnc_chrome:53.0 | 2.26 | 53.0.2785.143
| selenoid/chrome:54.0 | selenoid/vnc_chrome:54.0 | 2.27 | 54.0.2840.100
| selenoid/chrome:55.0 | selenoid/vnc_chrome:55.0 | 2.28 | 55.0.2883.87
| selenoid/chrome:56.0 | selenoid/vnc_chrome:56.0 | 2.29 | 56.0.2924.87
| selenoid/chrome:57.0 | selenoid/vnc_chrome:57.0 | 2.29 | 57.0.2987.110
| selenoid/chrome:58.0 | selenoid/vnc_chrome:58.0 | 2.29 | 58.0.3029.81
| selenoid/chrome:59.0 | selenoid/vnc_chrome:59.0 | 2.30 | 59.0.3071.86
| selenoid/chrome:60.0 | selenoid/vnc_chrome:60.0 | 2.31 | 60.0.3112.90
| selenoid/chrome:61.0 | selenoid/vnc_chrome:61.0 | 2.32 | 61.0.3163.79
| selenoid/chrome:62.0 | selenoid/vnc_chrome:62.0 | 2.33 | 62.0.3202.62
| selenoid/chrome:63.0 | selenoid/vnc_chrome:63.0 | 2.33 | 63.0.3239.84
| selenoid/chrome:64.0 | selenoid/vnc_chrome:64.0 | 2.35 | 64.0.3282.119
| selenoid/chrome:65.0 | selenoid/vnc_chrome:65.0 | 2.38 | 65.0.3325.181
| selenoid/chrome:66.0 | selenoid/vnc_chrome:66.0 | 2.38 | 66.0.3359.117
| selenoid/chrome:67.0 | selenoid/vnc_chrome:67.0 | 2.39 | 67.0.3396.62
| selenoid/chrome:68.0 | selenoid/vnc_chrome:68.0 | 2.41 | 68.0.3440.106
| selenoid/chrome:69.0 | selenoid/vnc_chrome:69.0 | 2.42 | 69.0.3497.100
| selenoid/chrome:70.0 | selenoid/vnc_chrome:70.0 | 2.44 | 70.0.3538.110
| selenoid/chrome:71.0 | selenoid/vnc_chrome:71.0 | 2.44 | 71.0.3578.80
| selenoid/chrome:72.0 | selenoid/vnc_chrome:72.0 | 2.46 | 72.0.3626.121
| selenoid/chrome:73.0 | selenoid/vnc_chrome:73.0 | 73.0.3683.68 | 73.0.3683.75
| selenoid/chrome:74.0 | selenoid/vnc_chrome:74.0 | 74.0.3729.6 | 74.0.3729.157
| selenoid/chrome:75.0 | selenoid/vnc_chrome:75.0 | 75.0.3770.90 | 75.0.3770.90
| selenoid/chrome:76.0 | selenoid/vnc_chrome:76.0 | 76.0.3809.87 | 76.0.3809.68
| selenoid/chrome:77.0 | selenoid/vnc_chrome:77.0 | 77.0.3865.40 | 77.0.3865.75
| selenoid/chrome:78.0 | selenoid/vnc_chrome:78.0 | 78.0.3904.87 | 78.0.3904.70
| selenoid/chrome:79.0 | selenoid/vnc_chrome:79.0 | 79.0.3945.36 | 79.0.3945.79
| selenoid/chrome:80.0 | selenoid/vnc_chrome:80.0 | 80.0.3987.106 | 80.0.3987.132
| selenoid/chrome:81.0 | selenoid/vnc_chrome:81.0 | 81.0.4044.138 | 81.0.4044.138
| - | - | - | 82.0.x.x (release skipped by development team)
| selenoid/chrome:83.0 | selenoid/vnc_chrome:83.0 | 83.0.4103.39 | 83.0.4103.61
| selenoid/chrome:84.0 | selenoid/vnc_chrome:84.0 | 84.0.4147.30 | 84.0.4147.89
| selenoid/chrome:85.0 | selenoid/vnc_chrome:85.0 | 85.0.4183.87 | 85.0.4183.121
| selenoid/chrome:86.0 | selenoid/vnc_chrome:86.0 | 86.0.4240.22 | 86.0.4240.75
| selenoid/chrome:87.0 | selenoid/vnc_chrome:87.0 | 87.0.4280.20 | 87.0.4280.66
| selenoid/chrome:88.0 | selenoid/vnc_chrome:88.0 | 88.0.4324.27 | 88.0.4324.96
| selenoid/chrome:89.0 | selenoid/vnc_chrome:89.0 | 89.0.4389.23 | 89.0.4389.72
| selenoid/chrome:90.0 | selenoid/vnc_chrome:90.0 | 90.0.4430.24 | 90.0.4430.72
| selenoid/chrome:91.0 | selenoid/vnc_chrome:91.0 | 91.0.4472.101 | 91.0.4472.106
| selenoid/chrome:92.0 | selenoid/vnc_chrome:92.0 | 92.0.4515.107 | 92.0.4515.131
| selenoid/chrome:93.0 | selenoid/vnc_chrome:93.0 | 93.0.4577.63 | 93.0.4577.63
| selenoid/chrome:94.0 | selenoid/vnc_chrome:94.0 | 94.0.4606.61 | 94.0.4606.61
| selenoid/chrome:95.0 | selenoid/vnc_chrome:95.0 | 95.0.4638.17 | 95.0.4638.54
| selenoid/chrome:96.0 | selenoid/vnc_chrome:96.0 | 96.0.4664.45 | 96.0.4664.45
| selenoid/chrome:97.0 | selenoid/vnc_chrome:97.0 | 97.0.4692.71 | 97.0.4692.71
| selenoid/chrome:98.0 | selenoid/vnc_chrome:97.0 | 98.0.4758.48 | 98.0.4758.80
| selenoid/chrome:99.0 | selenoid/vnc_chrome:99.0 | 99.0.4844.51 | 99.0.4844.51
| selenoid/chrome:100.0 | selenoid/vnc_chrome:100.0 | 100.0.4896.20 | 100.0.4896.60
| selenoid/chrome:101.0 | selenoid/vnc_chrome:101.0 | 101.0.4951.41 | 101.0.4951.54
|===
[NOTE]
====
. These images work with any modern Selenium client version.
. Images for older Chrome versions were not built because we have no Debian packages. If you have such packages - we could create more images.
====
+219
View File
@@ -0,0 +1,219 @@
== Cypress
These images are used to run https://cypress.io/[Cypress] tests in parallel.
.Chromium Images
|===
| Image | Aliases | Cypress version | Chromium version
| browsers/cypress-chromium:cypress-6.8.0 | - | 6.8.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-6.9.1 | - | 6.9.1 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.0.0 | - | 7.0.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.0.1 | - | 7.0.1 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.1.0 | - | 7.1.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.2.0 | - | 7.2.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.3.0 | - | 7.3.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.4.0 | - | 7.4.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.5.0 | - | 7.5.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.6.0 | - | 7.6.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-7.7.0 | - | 7.7.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-8.0.0 | - | 8.0.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-8.1.0 | - | 8.1.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-8.2.0 | - | 8.2.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-8.3.0 | browsers/cypress-chromium:91.0 | 8.3.0 | 91.0.4472.101
| browsers/cypress-chromium:cypress-8.3.1 | - | 8.3.1 | 92.0.4515.159
| browsers/cypress-chromium:cypress-8.4.0 | - | 8.4.0 | 92.0.4515.159
| browsers/cypress-chromium:cypress-8.4.1 | browsers/cypress-chromium:92.0 | 8.4.1 | 92.0.4515.159
| browsers/cypress-chromium:cypress-8.5.0 | browsers/cypress-chromium:93.0 | 8.5.0 | 93.0.4577.63
| browsers/cypress-chromium:cypress-8.6.0 | - | 8.6.0 | 94.0.4606.71
| browsers/cypress-chromium:cypress-8.7.0 | browsers/cypress-chromium:94.0 | 8.7.0 | 94.0.4606.81
| browsers/cypress-chromium:cypress-9.0.0 | - | 9.0.0 | 95.0.4638.69
| browsers/cypress-chromium:cypress-9.1.0 | - | 9.1.0 | 95.0.4638.69
| browsers/cypress-chromium:cypress-9.1.1 | - | 9.1.1 | 95.0.4638.69
| browsers/cypress-chromium:cypress-9.2.0 | - | 9.2.0 | 95.0.4638.69
| browsers/cypress-chromium:cypress-9.2.1 | browsers/cypress-chromium:95.0 | 9.2.1 | 95.0.4638.69
| browsers/cypress-chromium:cypress-9.3.0 | - | 9.3.0 | 97.0.4692.71
| browsers/cypress-chromium:cypress-9.3.1 | - | 9.3.1 | 97.0.4692.71
| browsers/cypress-chromium:cypress-9.4.0 | - | 9.4.0 | 97.0.4692.71
| browsers/cypress-chromium:cypress-9.4.1 | - | 9.4.1 | 97.0.4692.71
| browsers/cypress-chromium:cypress-9.5.0 | - | 9.5.0 | 97.0.4692.71
| browsers/cypress-chromium:cypress-9.5.1 | browsers/cypress-chromium:97.0 | 9.5.1 | 97.0.4692.71
| browsers/cypress-chromium:cypress-9.5.2 | - | 9.5.2 | 99.0.4844.51
| browsers/cypress-chromium:cypress-9.5.3 | - | 9.5.3 | 99.0.4844.84
| browsers/cypress-chromium:cypress-9.5.4 | browsers/cypress-chromium:99.0 | 9.5.4 | 99.0.4844.84
| browsers/cypress-chromium:cypress-9.6.0 | - | 9.6.0 | 100.0.4896.127
| browsers/cypress-chromium:cypress-9.6.1 | browsers/cypress-chromium:100.0 | 9.6.1 | 100.0.4896.127
|===
.Chrome Images
|===
| Image | Aliases | Cypress version | Chrome version
| browsers/cypress-chrome:cypress-6.8.0 | - | 6.8.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-6.9.1 | - | 6.9.1 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.0.0 | - | 7.0.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.0.1 | - | 7.0.1 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.1.0 | - | 7.1.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.2.0 | - | 7.2.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.3.0 | - | 7.3.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.4.0 | - | 7.4.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.5.0 | - | 7.5.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.6.0 | - | 7.6.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-7.7.0 | - | 7.7.0 | 91.0.4472.114
| browsers/cypress-chrome:cypress-8.0.0 | browsers/cypress-chrome:91.0 | 8.0.0 | 91.0.4472.164
| browsers/cypress-chrome:cypress-8.1.0 | - | 8.1.0 | 92.0.4515.107
| browsers/cypress-chrome:cypress-8.2.0 | - | 8.2.0 | 92.0.4515.131
| browsers/cypress-chrome:cypress-8.3.0 | - | 8.3.0 | 92.0.4515.159
| browsers/cypress-chrome:cypress-8.3.1 | browsers/cypress-chrome:92.0 | 8.3.1 | 92.0.4515.159
| browsers/cypress-chrome:cypress-8.4.0 | - | 8.4.0 | 93.0.4577.82
| browsers/cypress-chrome:cypress-8.4.1 | browsers/cypress-chrome:93.0 | 8.4.1 | 93.0.4577.82
| browsers/cypress-chrome:cypress-8.5.0 | - | 8.5.0 | 94.0.4606.61
| browsers/cypress-chrome:cypress-8.6.0 | browsers/cypress-chrome:94.0 | 8.6.0 | 94.0.4606.81
| browsers/cypress-chrome:cypress-8.7.0 | - | 8.7.0 | 95.0.4638.54
| browsers/cypress-chrome:cypress-9.0.0 | - | 9.0.0 | 96.0.4664.45
| browsers/cypress-chrome:cypress-9.1.0 | - | 9.1.0 | 96.0.4664.45
| browsers/cypress-chrome:cypress-9.1.1 | - | 9.1.1 | 96.0.4664.45
| browsers/cypress-chrome:cypress-9.2.0 | browsers/cypress-chrome:96.0 | 9.2.0 | 96.0.4664.110
| browsers/cypress-chrome:cypress-9.2.1 | - | 9.2.1 | 97.0.4692.71
| browsers/cypress-chrome:cypress-9.3.0 | - | 9.3.0 | 97.0.4692.71
| browsers/cypress-chrome:cypress-9.3.1 | browsers/cypress-chrome:97.0 | 9.3.1 | 97.0.4692.71
| browsers/cypress-chrome:cypress-9.4.0 | - | 9.4.0 | 98.0.4758.80
| browsers/cypress-chrome:cypress-9.4.1 | - | 9.4.1 | 98.0.4758.80
| browsers/cypress-chrome:cypress-9.5.0 | browsers/cypress-chrome:98.0 | 9.5.0 | 98.0.4758.102
| browsers/cypress-chrome:cypress-9.5.1 | - | 9.5.1 | 99.0.4844.51
| browsers/cypress-chrome:cypress-9.5.2 | browsers/cypress-chrome:99.0 | 9.5.2 | 99.0.4844.74
| browsers/cypress-chrome:cypress-9.5.3 | - | 9.5.3 | 100.0.4896.60
| browsers/cypress-chrome:cypress-9.5.4 | - | 9.5.4 | 100.0.4896.88
| browsers/cypress-chrome:cypress-9.6.0 | browsers/cypress-chrome:100.0 | 9.6.0 | 100.0.4896.127
| browsers/cypress-chrome:cypress-9.6.1 | browsers/cypress-chrome:101.0 | 9.6.1 | 101.0.4951.64
|===
.Electron Images
|===
| Image | Cypress version
| browsers/cypress-electron:cypress-6.8.0 | 6.8.0
| browsers/cypress-electron:cypress-6.9.1 | 6.9.1
| browsers/cypress-electron:cypress-7.0.0 | 7.0.0
| browsers/cypress-electron:cypress-7.0.1 | 7.0.1
| browsers/cypress-electron:cypress-7.1.0 | 7.1.0
| browsers/cypress-electron:cypress-7.2.0 | 7.2.0
| browsers/cypress-electron:cypress-7.3.0 | 7.3.0
| browsers/cypress-electron:cypress-7.4.0 | 7.4.0
| browsers/cypress-electron:cypress-7.5.0 | 7.5.0
| browsers/cypress-electron:cypress-7.6.0 | 7.6.0
| browsers/cypress-electron:cypress-7.7.0 | 7.7.0
| browsers/cypress-electron:cypress-8.0.0 | 8.0.0
| browsers/cypress-electron:cypress-8.1.0 | 8.1.0
| browsers/cypress-electron:cypress-8.2.0 | 8.2.0
| browsers/cypress-electron:cypress-8.3.0 | 8.3.0
| browsers/cypress-electron:cypress-8.3.1 | 8.3.1
| browsers/cypress-electron:cypress-8.4.0 | 8.4.0
| browsers/cypress-electron:cypress-8.4.1 | 8.4.1
| browsers/cypress-electron:cypress-8.5.0 | 8.5.0
| browsers/cypress-electron:cypress-8.6.0 | 8.6.0
| browsers/cypress-electron:cypress-8.7.0 | 8.7.0
| browsers/cypress-electron:cypress-9.0.0 | 9.0.0
| browsers/cypress-electron:cypress-9.1.0 | 9.1.0
| browsers/cypress-electron:cypress-9.1.1 | 9.1.1
| browsers/cypress-electron:cypress-9.2.0 | 9.2.0
| browsers/cypress-electron:cypress-9.2.1 | 9.2.1
| browsers/cypress-electron:cypress-9.3.0 | 9.3.0
| browsers/cypress-electron:cypress-9.3.1 | 9.3.1
| browsers/cypress-electron:cypress-9.4.0 | 9.4.0
| browsers/cypress-electron:cypress-9.4.1 | 9.4.1
| browsers/cypress-electron:cypress-9.5.0 | 9.5.0
| browsers/cypress-electron:cypress-9.5.1 | 9.5.1
| browsers/cypress-electron:cypress-9.5.2 | 9.5.2
| browsers/cypress-electron:cypress-9.5.3 | 9.5.3
| browsers/cypress-electron:cypress-9.5.4 | 9.5.4
| browsers/cypress-electron:cypress-9.6.0 | 9.6.0
| browsers/cypress-electron:cypress-9.6.1 | 9.6.1
|===
.Microsoft Edge Images
|===
| Image | Aliases | Cypress version | Edge version
| browsers/cypress-edge:cypress-6.8.0 | - | 6.8.0 | 92.0.902.15
| browsers/cypress-edge:cypress-6.9.0 | - | 6.9.0 | 92.0.902.15
| browsers/cypress-edge:cypress-6.9.1 | - | 6.9.1 | 92.0.902.15
| browsers/cypress-edge:cypress-7.0.0 | - | 7.0.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.0.1 | - | 7.0.1 | 92.0.902.15
| browsers/cypress-edge:cypress-7.1.0 | - | 7.1.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.2.0 | - | 7.2.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.3.0 | - | 7.3.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.4.0 | - | 7.4.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.5.0 | - | 7.5.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.6.0 | - | 7.6.0 | 92.0.902.15
| browsers/cypress-edge:cypress-7.7.0 | - | 7.7.0 | 92.0.902.40
| browsers/cypress-edge:cypress-8.0.0 | - | 8.0.0 | 92.0.902.49
| browsers/cypress-edge:cypress-8.1.0 | browsers/cypress-edge:92.0 | 8.1.0 | 92.0.902.62
| browsers/cypress-edge:cypress-8.2.0 | - | 8.2.0 | 93.0.961.11
| browsers/cypress-edge:cypress-8.3.0 | - | 8.3.0 | 93.0.961.18
| browsers/cypress-edge:cypress-8.3.1 | browsers/cypress-edge:93.0 | 8.3.1 | 93.0.961.33
| browsers/cypress-edge:cypress-8.4.0 | - | 8.4.0 | 94.0.992.19
| browsers/cypress-edge:cypress-8.4.1 | browsers/cypress-edge:94.0 | 8.4.1 | 94.0.992.23
| browsers/cypress-edge:cypress-8.5.0 | - | 8.5.0 | 95.0.1020.9
| browsers/cypress-edge:cypress-8.6.0 | - | 8.6.0 | 95.0.1020.20
| browsers/cypress-edge:cypress-8.7.0 | - | 8.7.0 | 95.0.1020.30
| browsers/cypress-edge:cypress-9.0.0 | browsers/cypress-edge:95.0 | 9.0.0 | 95.0.1020.53
| browsers/cypress-edge:cypress-9.1.0 | - | 9.1.0 | 96.0.4664.45
| browsers/cypress-edge:cypress-9.1.1 | - | 9.1.1 | 96.0.1054.43
| browsers/cypress-edge:cypress-9.2.0 | browsers/cypress-edge:96.0 | 9.2.0 | 96.0.1054.62
| browsers/cypress-edge:cypress-9.2.1 | - | 9.2.1 | 97.0.1072.55
| browsers/cypress-edge:cypress-9.3.0 | - | 9.3.0 | 97.0.1072.62
| browsers/cypress-edge:cypress-9.3.1 | - | 9.3.1 | 97.0.1072.62
| browsers/cypress-edge:cypress-9.4.0 | - | 9.4.0 | 97.0.1072.76
| browsers/cypress-edge:cypress-9.4.1 | browsers/cypress-edge:97.0 | 9.4.1 | 97.0.1072.76
| browsers/cypress-edge:cypress-9.5.0 | - | 9.5.0 | 98.0.1108.56
| browsers/cypress-edge:cypress-9.5.1 | browsers/cypress-edge:98.0 | 9.5.1 | 98.0.1108.62
| browsers/cypress-edge:cypress-9.5.2 | - | 9.5.2 | 99.0.1150.38
| browsers/cypress-edge:cypress-9.5.3 | browsers/cypress-edge:99.0 | 9.5.3 | 99.0.1150.55
| browsers/cypress-edge:cypress-9.5.4 | - | 9.5.4 | 100.0.1185.39
| browsers/cypress-edge:cypress-9.6.0 | browsers/cypress-edge:100.0 | 9.6.0 | 100.0.1185.50
| browsers/cypress-edge:cypress-9.6.1 | browsers/cypress-edge:101.0 | 9.6.1 | 101.0.1210.39
|===
.Firefox Images
|===
| Image | Aliases | Cypress version | Edge version
| browsers/cypress-firefox:cypress-6.8.0 | - | 6.8.0 | 89.0.1
| browsers/cypress-firefox:cypress-6.9.1 | - | 6.9.1 | 89.0.1
| browsers/cypress-firefox:cypress-7.0.0 | - | 7.0.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.0.1 | - | 7.0.1 | 89.0.1
| browsers/cypress-firefox:cypress-7.1.0 | - | 7.1.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.2.0 | - | 7.2.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.3.0 | - | 7.3.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.4.0 | - | 7.4.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.5.0 | - | 7.5.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.6.0 | - | 7.6.0 | 89.0.1
| browsers/cypress-firefox:cypress-7.7.0 | browsers/cypress-firefox:89.0 | 7.7.0 | 89.0.2
| browsers/cypress-firefox:cypress-8.0.0 | - | 8.0.0 | 90.0
| browsers/cypress-firefox:cypress-8.1.0 | - | 8.1.0 | 90.0
| browsers/cypress-firefox:cypress-8.2.0 | browsers/cypress-firefox:90.0 | 8.2.0 | 90.0.2
| browsers/cypress-firefox:cypress-8.3.0 | - | 8.3.0 | 91.0
| browsers/cypress-firefox:cypress-8.3.1 | browsers/cypress-firefox:91.0 | 8.3.1 | 91.0.2
| browsers/cypress-firefox:cypress-8.4.0 | - | 8.4.0 | 92.0
| browsers/cypress-firefox:cypress-8.4.1 | - | 8.4.1 | 92.0
| browsers/cypress-firefox:cypress-8.5.0 | browsers/cypress-firefox:92.0 | 8.5.0 | 92.0
| browsers/cypress-firefox:cypress-8.6.0 | - | 8.6.0 | 93.0
| browsers/cypress-firefox:cypress-8.7.0 | browsers/cypress-firefox:93.0 | 8.7.0 | 93.0
| browsers/cypress-firefox:cypress-9.0.0 | - | 9.0.0 | 94.0
| browsers/cypress-firefox:cypress-9.1.0 | - | 9.1.0 | 94.0
| browsers/cypress-firefox:cypress-9.1.1 | browsers/cypress-firefox:94.0 | 9.1.1 | 94.0
| browsers/cypress-firefox:cypress-9.2.0 | - | 9.2.0 | 95.0.1
| browsers/cypress-firefox:cypress-9.2.1 | browsers/cypress-firefox:95.0 | 9.2.1 | 95.0.1
| browsers/cypress-firefox:cypress-9.3.0 | - | 9.3.0 | 96.0
| browsers/cypress-firefox:cypress-9.3.1 | - | 9.3.1 | 96.0
| browsers/cypress-firefox:cypress-9.4.0 | - | 9.4.0 | 96.0
| browsers/cypress-firefox:cypress-9.4.1 | browsers/cypress-firefox:96.0 | 9.4.1 | 96.0
| browsers/cypress-firefox:cypress-9.5.0 | - | 9.5.0 | 97.0
| browsers/cypress-firefox:cypress-9.5.1 | browsers/cypress-firefox:97.0 | 9.5.1 | 97.0
| browsers/cypress-firefox:cypress-9.5.2 | - | 9.5.2 | 98.0
| browsers/cypress-firefox:cypress-9.5.3 | browsers/cypress-firefox:98.0 | 9.5.3 | 98.0.2
| browsers/cypress-firefox:cypress-9.5.4 | - | 9.5.4 | 99.0
| browsers/cypress-firefox:cypress-9.6.0 | browsers/cypress-firefox:99.0 | 9.6.0 | 99.0
| browsers/cypress-firefox:cypress-9.6.1 | browsers/cypress-firefox:100.0 | 9.6.1 | 100.0
|===
+26
View File
@@ -0,0 +1,26 @@
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-23854888-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-23854888-2');
</script>
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(43539754, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true,
webvisor:true,
trackHash:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/43539754" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
+11
View File
@@ -0,0 +1,11 @@
<base href="https://aerokube.com/images/{revnumber}/">
<meta property="og:image:height" content="1106">
<meta property="og:image:width" content="2113">
<meta property="og:title"
content="Browser Images | A set of browser images for Selenoid and Moon">
<meta property="og:description"
content="The most popular browser environments you need to run your tests">
<meta property="og:url" content="https://aerokube.com/images/{revnumber}">
<meta property="og:image"
content="https://aerokube.com/images/{revnumber}/img/og-image.jpg">
<meta name="twitter:site" content="@aerokube">
+26
View File
@@ -0,0 +1,26 @@
== Features
=== Adding Custom Root Certification Authority
In corporate networks tested environments are often using self-signed https://en.wikipedia.org/wiki/Transport_Layer_Security[TLS] certificates. Such certificates are issued by a https://en.wikipedia.org/wiki/Root_certificate[root certification authority] not known to browsers. When trying to open an HTTPS web-page using a self-signed certificate, your browser by default will refuse to do this saying that "Your connection is not private" or "This connection is untrusted". In Selenium tests you can use a standard capability (`acceptInsecureCerts = true`) to ignore such certificate errors but this will not work when your web-page is using https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security[HSTS].
In order to work properly with self-signed certificates, you have to add your root certification authority certificate to a list of trusted certificates. Our images allow to do this using environment variables. For every root certificate stored in `cert.pem` file:
. Encode `cert.pem` file contents to https://en.wikipedia.org/wiki/Base64[Base64]:
CERT_CONTENTS=$(cat cert.pem | base64 -w0)
+
In case of MacOS respective command would be:
CERT_CONTENTS=$(cat cert.pem | base64)
. Set an environment variable to browser image:
ROOT_CA_<cert-name>="$CERT_CONTENTS"
+
In that case `<cert-name>` will be used as certificate name in the browser certificates storage. For example:
ROOT_CA_MY_CERT="LS0tL....=="
+119
View File
@@ -0,0 +1,119 @@
== Firefox
.Firefox Images with Selenium Server
|===
| Image | VNC Image | Selenium Version | Firefox Version | Client Version
| selenoid/firefox:3.6 | selenoid/vnc_firefox:3.6 | 2.20.0 | 3.6.16 i386 (dialogs may not work) .7+<.^|
**Java:** 2.53.1 and below
**Python:** not supported
**selenium-webdriver.js:** not supported
| selenoid/firefox:4.0 | selenoid/vnc_firefox:4.0 | 2.20.0 | 4.0.1 i386
| selenoid/firefox:5.0 | selenoid/vnc_firefox:5.0 | 2.20.0 | 5.0.1 i386
| selenoid/firefox:6.0 | selenoid/vnc_firefox:6.0 | 2.20.0 | 6.0.2 i386
| selenoid/firefox:7.0 | selenoid/vnc_firefox:7.0 | 2.20.0 | 7.0.1 i386
| selenoid/firefox:8.0 | selenoid/vnc_firefox:8.0 | 2.20.0 | 8.0.1 i386
| selenoid/firefox:9.0 | selenoid/vnc_firefox:9.0 | 2.20.0 | 9.0.1
| selenoid/firefox:10.0 | selenoid/vnc_firefox:10.0 | 2.32.0 | 10.0.2 .13+<.^|
**Java:** any modern version
**Python:** not supported
**selenium-webdriver.js:** not supported
| selenoid/firefox:11.0 | selenoid/vnc_firefox:11.0 | 2.32.0 | 11.0
| selenoid/firefox:12.0 | selenoid/vnc_firefox:12.0 | 2.32.0 | 12.0
| selenoid/firefox:13.0 | selenoid/vnc_firefox:13.0 | 2.32.0 | 13.0
| selenoid/firefox:14.0 | selenoid/vnc_firefox:14.0 | 2.32.0 | 14.0.1
| selenoid/firefox:15.0 | selenoid/vnc_firefox:15.0 | 2.32.0 | 15.0.1
| selenoid/firefox:16.0 | selenoid/vnc_firefox:16.0 | 2.32.0 | 16.0.2
| selenoid/firefox:17.0 | selenoid/vnc_firefox:17.0 | 2.32.0 | 17.0.1
| selenoid/firefox:18.0 | selenoid/vnc_firefox:18.0 | 2.32.0 | 18.0.2
| selenoid/firefox:19.0 | selenoid/vnc_firefox:19.0 | 2.32.0 | 19.0.2
| selenoid/firefox:20.0 | selenoid/vnc_firefox:20.0 | 2.32.0 | 20.0
| selenoid/firefox:21.0 | selenoid/vnc_firefox:21.0 | 2.32.0 | 21.0
| selenoid/firefox:22.0 | selenoid/vnc_firefox:22.0 | 2.32.0 | 22.0
| selenoid/firefox:23.0 | selenoid/vnc_firefox:23.0 | 2.35.0 | 23.0.1 .25+<.^| Any modern client version
| selenoid/firefox:24.0 | selenoid/vnc_firefox:24.0 | 2.39.0 | 24.0
| selenoid/firefox:25.0 | selenoid/vnc_firefox:25.0 | 2.39.0 | 25.0.1
| selenoid/firefox:26.0 | selenoid/vnc_firefox:26.0 | 2.39.0 | 26.0
| selenoid/firefox:27.0 | selenoid/vnc_firefox:27.0 | 2.40.0 | 27.0.1
| selenoid/firefox:28.0 | selenoid/vnc_firefox:28.0 | 2.41.0 | 28.0
| selenoid/firefox:29.0 | selenoid/vnc_firefox:29.0 | 2.43.1 | 29.0.1
| selenoid/firefox:30.0 | selenoid/vnc_firefox:30.0 | 2.43.1 | 30.0
| selenoid/firefox:31.0 | selenoid/vnc_firefox:31.0 | 2.44.0 | 31.0
| selenoid/firefox:32.0 | selenoid/vnc_firefox:32.0 | 2.44.0 | 32.0.3
| selenoid/firefox:33.0 | selenoid/vnc_firefox:33.0 | 2.44.0 | 33.0.3
| selenoid/firefox:34.0 | selenoid/vnc_firefox:34.0 | 2.45.0 | 34.0.5
| selenoid/firefox:35.0 | selenoid/vnc_firefox:35.0 | 2.45.0 | 35.0.1
| selenoid/firefox:36.0 | selenoid/vnc_firefox:36.0 | 2.45.0 | 36.0.1
| selenoid/firefox:37.0 | selenoid/vnc_firefox:37.0 | 2.45.0 | 37.0.2
| selenoid/firefox:38.0 | selenoid/vnc_firefox:38.0 | 2.45.0 | 38.0.5
| selenoid/firefox:39.0 | selenoid/vnc_firefox:39.0 | 2.45.0 | 39.0.3
| selenoid/firefox:40.0 | selenoid/vnc_firefox:40.0 | 2.45.0 | 40.0.3
| selenoid/firefox:41.0 | selenoid/vnc_firefox:41.0 | 2.45.0 | 41.0.2
| selenoid/firefox:42.0 | selenoid/vnc_firefox:42.0 | 2.47.1 | 42.0
| selenoid/firefox:43.0 | selenoid/vnc_firefox:43.0 | 2.53.1 | 43.0.4
| selenoid/firefox:44.0 | selenoid/vnc_firefox:44.0 | 2.53.1 | 44.0.2
| selenoid/firefox:45.0 | selenoid/vnc_firefox:45.0 | 2.53.1 | 45.0.2
| selenoid/firefox:46.0 | selenoid/vnc_firefox:46.0 | 2.53.1 | 46.0.1
| selenoid/firefox:47.0 | selenoid/vnc_firefox:47.0 | 2.53.1 | 47.0.1
|===
WARNING: Firefox 53.0+ images require Selenium client 3.4.0 or newer for Java and Node.js and 3.5.0 or newer for Python.
.Firefox Images with Selenoid
|===
| Image | VNC Image | Selenoid Version | Geckodriver Version | Firefox Version
| selenoid/firefox:48.0 | selenoid/vnc_firefox:48.0 | 1.10.0 | 0.13.0 | 48.0.2 (page load timeout, native events and proxies don't work)
| selenoid/firefox:49.0 | selenoid/vnc_firefox:49.0 | 1.10.0 | 0.13.0 | 49.0.2 (page load timeout, native events and switching between windows don't work)
| selenoid/firefox:50.0 | selenoid/vnc_firefox:50.0 | 1.10.0 | 0.13.0 | 50.0.2 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:51.0 | selenoid/vnc_firefox:51.0 | 1.10.0 | 0.14.0 | 51.0.1 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:52.0 | selenoid/vnc_firefox:52.0 | 1.10.0 | 0.15.0 | 52.0.2 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:53.0 | selenoid/vnc_firefox:53.0 | 1.10.0 | 0.16.0 | 53.0.2 (switching windows may not work)
| selenoid/firefox:54.0 | selenoid/vnc_firefox:54.0 | 1.10.0 | 0.17.0 | 54.0.1 (switching windows may not work)
| selenoid/firefox:55.0 | selenoid/vnc_firefox:55.0 | 1.10.0 | 0.18.0 | 55.0.1 (switching windows may not work)
| selenoid/firefox:56.0 | selenoid/vnc_firefox:56.0 | 1.10.0 | 0.19.1 | 56.0.1
| selenoid/firefox:57.0 | selenoid/vnc_firefox:57.0 | 1.10.0 | 0.19.1 | 57.0
| selenoid/firefox:58.0 | selenoid/vnc_firefox:58.0 | 1.10.0 | 0.20.1 | 58.0
| selenoid/firefox:59.0 | selenoid/vnc_firefox:59.0 | 1.10.0 | 0.20.1 | 59.0.1
| selenoid/firefox:60.0 | selenoid/vnc_firefox:60.0 | 1.10.0 | 0.21.0 | 60.0.2
| selenoid/firefox:61.0 | selenoid/vnc_firefox:61.0 | 1.10.0 | 0.21.0 | 61.0
| selenoid/firefox:62.0 | selenoid/vnc_firefox:62.0 | 1.10.0 | 0.22.0 | 62.0
| selenoid/firefox:63.0 | selenoid/vnc_firefox:63.0 | 1.8.1 | 0.23.0 | 63.0
| selenoid/firefox:64.0 | selenoid/vnc_firefox:64.0 | 1.8.4 | 0.23.0 | 64.0
| selenoid/firefox:65.0 | selenoid/vnc_firefox:65.0 | 1.9.0 | 0.24.0 | 65.0
| selenoid/firefox:66.0 | selenoid/vnc_firefox:66.0 | 1.9.1 | 0.24.0 | 66.0.1
| selenoid/firefox:67.0 | selenoid/vnc_firefox:67.0 | 1.9.1 | 0.24.0 | 67.0
| selenoid/firefox:68.0 | selenoid/vnc_firefox:68.0 | 1.9.2 | 0.24.0 | 68.0
| selenoid/firefox:69.0 | selenoid/vnc_firefox:69.0 | 1.9.2 | 0.24.0 | 69.0
| selenoid/firefox:70.0 | selenoid/vnc_firefox:70.0 | 1.9.2 | 0.26.0 | 70.0
| selenoid/firefox:71.0 | selenoid/vnc_firefox:71.0 | 1.9.3 | 0.26.0 | 71.0
| selenoid/firefox:72.0 | selenoid/vnc_firefox:72.0 | 1.9.3 | 0.26.0 | 72.0
| selenoid/firefox:73.0 | selenoid/vnc_firefox:73.0 | 1.10.0 | 0.26.0 | 73.0
| selenoid/firefox:74.0 | selenoid/vnc_firefox:74.0 | 1.10.0 | 0.26.0 | 74.0.1
| selenoid/firefox:75.0 | selenoid/vnc_firefox:75.0 | 1.10.0 | 0.26.0 | 75.0
| selenoid/firefox:76.0 | selenoid/vnc_firefox:76.0 | 1.10.0 | 0.26.0 | 76.0
| selenoid/firefox:77.0 | selenoid/vnc_firefox:77.0 | 1.10.0 | 0.26.0 | 77.0.1
| selenoid/firefox:78.0 | selenoid/vnc_firefox:78.0 | 1.10.0 | 0.26.0 | 78.0.1
| selenoid/firefox:79.0 | selenoid/vnc_firefox:79.0 | 1.10.0 | 0.27.0 | 79.0
| selenoid/firefox:80.0 | selenoid/vnc_firefox:80.0 | 1.10.0 | 0.27.0 | 80.0
| selenoid/firefox:81.0 | selenoid/vnc_firefox:81.0 | 1.10.0 | 0.27.0 | 81.0
| selenoid/firefox:82.0 | selenoid/vnc_firefox:82.0 | 1.10.0 | 0.27.0 | 82.0
| selenoid/firefox:83.0 | selenoid/vnc_firefox:83.0 | 1.10.0 | 0.28.0 | 83.0
| selenoid/firefox:84.0 | selenoid/vnc_firefox:84.0 | 1.10.0 | 0.28.0 | 84.0
| selenoid/firefox:85.0 | selenoid/vnc_firefox:85.0 | 1.10.1 | 0.29.0 | 85.0.1
| selenoid/firefox:86.0 | selenoid/vnc_firefox:86.0 | 1.10.1 | 0.29.0 | 86.0
| selenoid/firefox:87.0 | selenoid/vnc_firefox:87.0 | 1.10.1 | 0.29.0 | 87.0
| selenoid/firefox:88.0 | selenoid/vnc_firefox:88.0 | 1.10.3 | 0.29.1 | 88.0
| selenoid/firefox:89.0 | selenoid/vnc_firefox:89.0 | 1.10.3 | 0.29.1 | 89.0
| selenoid/firefox:90.0 | selenoid/vnc_firefox:90.0 | 1.10.3 | 0.29.1 | 90.0
| selenoid/firefox:91.0 | selenoid/vnc_firefox:91.0 | 1.10.3 | 0.29.1 | 91.0
| selenoid/firefox:92.0 | selenoid/vnc_firefox:92.0 | 1.10.5 | 0.29.1 | 92.0
| selenoid/firefox:93.0 | selenoid/vnc_firefox:93.0 | 1.10.5 | 0.29.1 | 93.0
| selenoid/firefox:94.0 | selenoid/vnc_firefox:94.0 | 1.10.5 | 0.29.1 | 94.0
| selenoid/firefox:95.0 | selenoid/vnc_firefox:95.0 | 1.10.7 | 0.29.1 | 95.0
| selenoid/firefox:96.0 | selenoid/vnc_firefox:96.0 | 1.10.7 | 0.29.1 | 96.0
| selenoid/firefox:97.0 | selenoid/vnc_firefox:97.0 | 1.10.7 | 0.29.1 | 97.0
| selenoid/firefox:98.0 | selenoid/vnc_firefox:98.0 | 1.10.7 | 0.29.1 | 98.0
| selenoid/firefox:99.0 | selenoid/vnc_firefox:99.0 | 1.10.7 | 0.29.1 | 99.0
| selenoid/firefox:100.0 | selenoid/vnc_firefox:100.0 | 1.10.7 | 0.29.1 | 100.0
|===
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

+37
View File
@@ -0,0 +1,37 @@
= Browser Images
:revdate: {docdate}
:toc: left
:toclevels: 3
:sectnums:
:sectanchors:
:Author: Aerokube Software OÜ
:revnumber: latest
:icons: font
:source-highlighter: coderay
:docinfo: shared
:title: Browser Images - A set of browser images for Selenoid and Moon
:description: The most popular browser environments you need to run your tests
:favicon: img/favicon.png
This reference covers version: _{revnumber}_
This section contains information about browser images available for using with https://aerokube.com/selenoid/[Selenoid] and https://aerokube.com/moon/[Moon]. Please refer to https://github.com/aerokube/images[GitHub repository] if you need source code.
== Browser Image Information
include::cdtp.adoc[leveloffset=+1]
include::cypress.adoc[leveloffset=+1]
include::playwright.adoc[leveloffset=+1]
=== Selenium
include::android.adoc[leveloffset=+2]
include::chrome.adoc[leveloffset=+2]
include::chrome-mobile.adoc[leveloffset=+2]
include::firefox.adoc[leveloffset=+2]
include::msedge.adoc[leveloffset=+2]
include::opera.adoc[leveloffset=+2]
include::safari.adoc[leveloffset=+2]
include::windows.adoc[leveloffset=+2]
include::features.adoc[]
include::building-images.adoc[]
+23
View File
@@ -0,0 +1,23 @@
== Microsoft Edge
IMPORTANT: When adding Microsoft Edge to browsers.json - you have to use MicrosoftEdge as browser name.
.Microsoft Edge Images
|===
| Image | msedgedriver version | Edge version
| browsers/edge:88.0 | 88.0.673.0 | 88.0.673.0 (dev)
| browsers/edge:89.0 | 89.0.731.0 | 89.0.731.0 (dev)
| browsers/edge:90.0 | 90.0.789.1 | 90.0.789.1 (dev)
| browsers/edge:91.0 | 91.0.831.1 | 91.0.831.1 (dev)
| browsers/edge:92.0 | 92.0.902.9 | 92.0.902.62 (dev)
| browsers/edge:93.0 | 93.0.961.2 | 93.0.961.18 (dev)
| browsers/edge:94.0 | 94.0.992.19 | 94.0.992.19 (beta)
| browsers/edge:95.0 | 95.0.1020.5 | 95.0.1020.9 (beta)
| browsers/edge:96.0 | 96.0.1054.34 | 96.0.1054.34
| browsers/edge:97.0 | 97.0.1072.8 | 97.0.1072.55
| browsers/edge:98.0 | 98.0.1108.55 | 98.0.1108.56
| browsers/edge:99.0 | 99.0.1150.7 | 99.0.1150.30
| browsers/edge:100.0 | 100.0.1185.23 | 100.0.1185.39
| browsers/edge:101.0 | 100.0.1185.23 | 100.0.1185.39
|===
+83
View File
@@ -0,0 +1,83 @@
== Opera
.Opera Presto Images
|===
| Image | VNC Image | Selenium version | Opera version
| selenoid/opera:12.16 | selenoid/vnc_opera:12.16 | 2.37.0 | 12.16.1860 (dialogs and probably async JS don't work)
|===
[WARNING]
====
Due to bug in *Operadriver* to work with *Opera Blink* images you need to pass additional capability:
[source,javascript]
{"browserName": "opera", "operaOptions": {"binary": "/usr/bin/opera"}}
We do not consider these images really stable. Many of base operations like working with proxies may not work.
====
.Opera Blink Images
|===
| Image | VNC Image | Operadriver version | Opera version
| selenoid/opera:33.0 | selenoid/vnc_opera:33.0 | 0.2.2 | 33.0.1990.115
| selenoid/opera:34.0 | selenoid/vnc_opera:34.0 | 0.2.2 | 34.0.2036.50
| selenoid/opera:35.0 | selenoid/vnc_opera:35.0 | 0.2.2 | 35.0.2066.92
| selenoid/opera:36.0 | selenoid/vnc_opera:36.0 | 0.2.2 | 36.0.2130.65
| selenoid/opera:37.0 | selenoid/vnc_opera:37.0 | 0.2.2 | 37.0.2178.54
| selenoid/opera:38.0 | selenoid/vnc_opera:38.0 | 0.2.2 | 38.0.2220.41
| selenoid/opera:39.0 | selenoid/vnc_opera:39.0 | 0.2.2 | 39.0.2256.71
| selenoid/opera:40.0 | selenoid/vnc_opera:40.0 | 0.2.2 | 40.0.2308.90
| selenoid/opera:41.0 | selenoid/vnc_opera:41.0 | 2.27 | 41.0.2353.69
| selenoid/opera:42.0 | selenoid/vnc_opera:42.0 | 2.27 | 42.0.2393.94
| selenoid/opera:43.0 | selenoid/vnc_opera:43.0 | 2.27 | 43.0.2442.991
| selenoid/opera:44.0 | selenoid/vnc_opera:44.0 | 2.27 | 44.0.2510.857
| selenoid/opera:45.0 | selenoid/vnc_opera:45.0 | 2.27 | 45.0.2552.635
| selenoid/opera:46.0 | selenoid/vnc_opera:46.0 | 2.27 | 46.0.2597.26
| selenoid/opera:47.0 | selenoid/vnc_opera:47.0 | 2.29 | 47.0.2631.39
| selenoid/opera:48.0 | selenoid/vnc_opera:48.0 | 2.30 | 48.0.2685.35
| selenoid/opera:49.0 | selenoid/vnc_opera:49.0 | 2.32 | 49.0.2725.39
| selenoid/opera:50.0 | selenoid/vnc_opera:50.0 | 2.32 | 50.0.2762.45
| selenoid/opera:51.0 | selenoid/vnc_opera:51.0 | 2.33 | 51.0.2830.26
| selenoid/opera:52.0 | selenoid/vnc_opera:52.0 | 2.35 | 52.0.2871.37
| selenoid/opera:53.0 | selenoid/vnc_opera:53.0 | 2.36 | 53.0.2907.68
| selenoid/opera:54.0 | selenoid/vnc_opera:54.0 | 2.37 | 54.0.2952.46
| selenoid/opera:55.0 | selenoid/vnc_opera:55.0 | 2.37 | 55.0.2994.37
| selenoid/opera:56.0 | selenoid/vnc_opera:56.0 | 2.40 | 56.0.3051.31
| selenoid/opera:57.0 | selenoid/vnc_opera:57.0 | 2.41 | 57.0.3098.76
| selenoid/opera:58.0 | selenoid/vnc_opera:58.0 | 2.42 | 58.0.3135.79
| - | - | - | 59.0.x.x (no stable release exists)
| selenoid/opera:60.0 | selenoid/vnc_opera:60.0 | 2.45 | 60.0.3255.56
| - | - | - | 61.0.x.x (no stable release exists)
| selenoid/opera:62.0 | selenoid/vnc_opera:62.0 | 75.0.3770.100 | 62.0.3331.99 (need to use browserName = chrome)
| selenoid/opera:63.0 | selenoid/vnc_opera:63.0 | 76.0.3809.132 | 63.0.3368.91
| selenoid/opera:64.0 | selenoid/vnc_opera:64.0 | 77.0.3865.120 | 64.0.3417.73
| selenoid/opera:65.0 | selenoid/vnc_opera:65.0 | 78.0.3904.87 | 65.0.3467.42
| selenoid/opera:66.0 | selenoid/vnc_opera:66.0 | 79.0.3945.79 | 66.0.3515.36
| selenoid/opera:67.0 | selenoid/vnc_opera:67.0 | 80.0.3987.100 | 67.0.3575.53
| selenoid/opera:68.0 | selenoid/vnc_opera:68.0 | 81.0.4044.113 | 68.0.3618.104
| selenoid/opera:69.0 | selenoid/vnc_opera:69.0 | 83.0.4103.97 | 69.0.3686.49
| selenoid/opera:70.0 | selenoid/vnc_opera:70.0 | 84.0.4147.89 | 70.0.3728.133
| selenoid/opera:71.0 | selenoid/vnc_opera:71.0 | 85.0.4183.102 | 71.0.3770.228
| selenoid/opera:72.0 | selenoid/vnc_opera:72.0 | 86.0.4240.80 | 72.0.3815.378
| selenoid/opera:73.0 | selenoid/vnc_opera:73.0 | 87.0.4280.67 | 73.0.3856.329
| selenoid/opera:74.0 | selenoid/vnc_opera:74.0 | 88.0.4324.104 | 74.0.3911.107
| selenoid/opera:75.0 | selenoid/vnc_opera:75.0 | 89.0.4389.82 | 75.0.3969.93
| selenoid/opera:76.0 | selenoid/vnc_opera:76.0 | 90.0.4430.85 | 76.0.4017.107
| selenoid/opera:77.0 | selenoid/vnc_opera:77.0 | 91.0.4472.77 | 77.0.4054.90
| selenoid/opera:78.0 | selenoid/vnc_opera:78.0 | 92.0.4515.107 | 78.0.4093.184
| selenoid/opera:79.0 | selenoid/vnc_opera:79.0 | 93.0.4577.63 | 79.0.4143.22
| selenoid/opera:80.0 | selenoid/vnc_opera:80.0 | 94.0.4606.61 | 80.0.4170.16
| selenoid/opera:81.0 | selenoid/vnc_opera:81.0 | 95.0.4638.54 | 81.0.4196.31
| selenoid/opera:82.0 | selenoid/vnc_opera:82.0 | 96.0.4664.45 | 82.0.4227.23
| selenoid/opera:83.0 | selenoid/vnc_opera:83.0 | 97.0.4692.71 | 83.0.4254.19
| selenoid/opera:84.0 | selenoid/vnc_opera:84.0 | 98.0.4758.82 | 84.0.4316.21
| selenoid/opera:85.0 | selenoid/vnc_opera:85.0 | 99.0.4844.51 | 85.0.4341.18
| selenoid/opera:86.0 | selenoid/vnc_opera:86.0 | 100.0.4896.127 | 86.0.4363.50
|===
[NOTE]
====
. These images work with any modern Selenium client version.
. Images for older Opera versions were not built because we have no Debian packages. If you have such packages - we could create more images.
====
+195
View File
@@ -0,0 +1,195 @@
== Playwright
These images are used to run https://github.com/microsoft/playwright[Playwright] tests in parallel.
.Chromium Images
|===
| Image | Aliases | Playwright version | Chromium version
| playwright/chromium:playwright-1.1.1 | - | 1.1.1 | 85.0.4165.0
| playwright/chromium:playwright-1.2.0 | playwright/chromium:85.0 | 1.2.0 | 85.0.4182.0
| playwright/chromium:playwright-1.3.0 | - | 1.3.0 | 86.0.4217.0
| playwright/chromium:playwright-1.4.0 | playwright/chromium:86.0 | 1.4.0 | 86.0.4238.0
| playwright/chromium:playwright-1.5.1 | - | 1.5.1 | 88.0.4287.0
| playwright/chromium:playwright-1.6.1 | playwright/chromium:88.0 | 1.6.1 | 88.0.4316.0
| playwright/chromium:playwright-1.7.0 | playwright/chromium:89.0 | 1.7.0 | 89.0.4344.0
| playwright/chromium:playwright-1.8.0 | - | 1.8.0 | 90.0.4392.0
| playwright/chromium:playwright-1.8.1 | - | 1.8.1 | 90.0.4392.0
| playwright/chromium:playwright-1.9.0 | - | 1.9.0 | 90.0.4421.0
| playwright/chromium:playwright-1.9.1 | - | 1.9.1 | 90.0.4421.0
| playwright/chromium:playwright-1.9.2 | - | 1.9.2 | 90.0.4421.0
| playwright/chromium:playwright-1.10.0 | playwright/chromium:90.0 | 1.10.0 | 90.0.4430.0
| playwright/chromium:playwright-1.11.0 | - | 1.11.0 | 92.0.4498.0
| playwright/chromium:playwright-1.11.1 | playwright/chromium:92.0 | 1.11.0 | 92.0.4498.0
| playwright/chromium:playwright-1.12.0 | - | 1.12.0 | 93.0.4530.0
| playwright/chromium:playwright-1.12.1 | - | 1.12.1 | 93.0.4530.0
| playwright/chromium:playwright-1.12.2 | - | 1.12.2 | 93.0.4530.0
| playwright/chromium:playwright-1.12.3 | - | 1.12.3 | 93.0.4530.0
| playwright/chromium:playwright-1.13.0 | - | 1.13.0 | 93.0.4576.0
| playwright/chromium:playwright-1.13.1 | playwright/chromium:93.0 | 1.13.1 | 93.0.4576.0
| playwright/chromium:playwright-1.14.0 | - | 1.14.0 | 94.0.4595.0
| playwright/chromium:playwright-1.14.1 | playwright/chromium:94.0 | 1.14.1 | 94.0.4595.0
| playwright/chromium:playwright-1.15.0 | - | 1.15.0 | 96.0.4641.0
| playwright/chromium:playwright-1.15.1 | - | 1.15.1 | 96.0.4641.0
| playwright/chromium:playwright-1.15.2 | playwright/chromium:96.0 | 1.15.2 | 96.0.4641.0
| playwright/chromium:playwright-1.16.0 | - | 1.16.0 | 97.0.4666.0
| playwright/chromium:playwright-1.16.1 | - | 1.16.1 | 97.0.4666.0
| playwright/chromium:playwright-1.16.2 | - | 1.16.2 | 97.0.4666.0
| playwright/chromium:playwright-1.16.3 | playwright/chromium:97.0 | 1.16.3 | 97.0.4666.0
| playwright/chromium:playwright-1.17.0 | - | 1.17.0 | 98.0.4695.0
| playwright/chromium:playwright-1.17.1 | - | 1.17.1 | 98.0.4695.0
| playwright/chromium:playwright-1.17.2 | playwright/chromium:98.0 | 1.17.2 | 98.0.4695.0
| playwright/chromium:playwright-1.18.0 | - | 1.18.0 | 99.0.4812.0
| playwright/chromium:playwright-1.18.1 | playwright/chromium:99.0 | 1.18.1 | 99.0.4812.0
| playwright/chromium:playwright-1.19.0 | - | 1.19.0 | 100.0.4863.0
| playwright/chromium:playwright-1.19.1 | - | 1.19.1 | 100.0.4863.0
| playwright/chromium:playwright-1.19.2 | playwright/chromium:100.0 | 1.19.2 | 100.0.4863.0
| playwright/chromium:playwright-1.20.0 | - | 1.20.0 | 101.0.4921.0
| playwright/chromium:playwright-1.20.1 | - | 1.20.1 | 101.0.4921.0
| playwright/chromium:playwright-1.20.2 | - | 1.20.2 | 101.0.4921.0
| playwright/chromium:playwright-1.21.0 | - | 1.21.0 | 101.0.4951.26
| playwright/chromium:playwright-1.21.1 | playwright/chromium:101.0 | 1.21.1 | 101.0.4951.26
| playwright/chromium:playwright-1.22.0 | playwright/chromium:102.0 | 1.22.0 | 102.0.5005.40
|===
.Chrome Images
|===
| Image | Aliases | Playwright version | Chrome version
| playwright/chrome:playwright-1.7.1 | playwright/chrome:89.0 | 1.7.1 | 89.0.4389.58
| playwright/chrome:playwright-1.8.1 | - | 1.8.1 | 90.0.4421.5
| playwright/chrome:playwright-1.9.0 | - | 1.9.0 | 90.0.4421.5
| playwright/chrome:playwright-1.9.1 | - | 1.9.1 | 90.0.4427.5
| playwright/chrome:playwright-1.9.2 | playwright/chrome:90.0 | 1.9.2 | 90.0.4430.19
| playwright/chrome:playwright-1.10.0 | - | 1.10.0 | 91.0.4455.2
| playwright/chrome:playwright-1.11.0 | - | 1.11.0 | 92.0.4503.0
| playwright/chrome:playwright-1.11.1 | playwright/chrome:92.0 | 1.11.0 | 92.0.4503.0
| playwright/chrome:playwright-1.12.0 | - | 1.12.0 | 91.0.4472.101
| playwright/chrome:playwright-1.12.1 | - | 1.12.1 | 91.0.4472.101
| playwright/chrome:playwright-1.12.2 | - | 1.12.2 | 91.0.4472.101
| playwright/chrome:playwright-1.12.3 | playwright/chrome:91.0 (switched to stable releases) | 1.12.3 | 91.0.4472.114
| playwright/chrome:playwright-1.13.0 | - | 1.13.0 | 92.0.4515.107
| playwright/chrome:playwright-1.13.1 | - | 1.13.1 | 92.0.4515.107
| playwright/chrome:playwright-1.14.0 | - | 1.14.0 | 92.0.4515.131
| playwright/chrome:playwright-1.14.1 | - | 1.14.1 | 93.0.4577.63
| playwright/chrome:playwright-1.15.0 | playwright/chrome:93.0 | 1.15.0 | 93.0.4577.82
| playwright/chrome:playwright-1.15.1 | - | 1.15.1 | 94.0.4606.71
| playwright/chrome:playwright-1.15.2 | playwright/chrome:94.0 | 1.15.2 | 94.0.4606.71
| playwright/chrome:playwright-1.16.0 | - | 1.16.0 | 95.0.4638.54
| playwright/chrome:playwright-1.16.1 | - | 1.16.1 | 95.0.4638.54
| playwright/chrome:playwright-1.16.2 | - | 1.16.2 | 95.0.4638.54
| playwright/chrome:playwright-1.16.3 | playwright/chrome:95.0 | 1.16.2 | 95.0.4638.69
| playwright/chrome:playwright-1.17.0 | - | 1.17.0 | 96.0.4664.45
| playwright/chrome:playwright-1.17.1 | playwright/chrome:96.0 | 1.17.1 | 96.0.4664.45
| playwright/chrome:playwright-1.17.2 | - | 1.17.2 | 97.0.4692.71
| playwright/chrome:playwright-1.18.0 | - | 1.18.0 | 97.0.4692.99
| playwright/chrome:playwright-1.18.1 | playwright/chrome:97.0 | 1.18.1 | 97.0.4692.99
| playwright/chrome:playwright-1.19.0 | - | 1.19.0 | 98.0.4758.80
| playwright/chrome:playwright-1.19.1 | - | 1.19.1 | 98.0.4758.102
| playwright/chrome:playwright-1.19.2 | playwright/chrome:98.0 | 1.19.2 | 98.0.4758.102
| playwright/chrome:playwright-1.20.0 | - | 1.20.0 | 99.0.4844.74
| playwright/chrome:playwright-1.20.1 | playwright/chrome:99.0 | 1.20.1 | 99.0.4844.82
| playwright/chrome:playwright-1.20.2 | - | 1.20.2 | 100.0.4896.75
| playwright/chrome:playwright-1.21.0 | - | 1.21.0 | 100.0.4896.127
| playwright/chrome:playwright-1.21.1 | playwright/chrome:100.0 | 1.21.1 | 100.0.4896.127
| playwright/chrome:playwright-1.22.0 | playwright/chrome:101.0 | 1.22.0 | 101.0.4951.64
|===
.Firefox Images
|===
| Image | Aliases | Playwright version | Firefox version
| playwright/firefox:playwright-1.1.1 | playwright/firefox:77.0 | 1.1.1 | 77.0b3
| playwright/firefox:playwright-1.2.0 | playwright/firefox:78.0 | 1.2.0 | 78.0b5
| playwright/firefox:playwright-1.3.0 | - | 1.3.0 | 78.0b5
| playwright/firefox:playwright-1.4.0 | playwright/firefox:80.0 | 1.4.0 | 80.0b8
| playwright/firefox:playwright-1.5.1 | playwright/firefox:82.0 | 1.5.1 | 82.0b9
| playwright/firefox:playwright-1.6.1 | playwright/firefox:83.0 | 1.6.1 | 83.0b8
| playwright/firefox:playwright-1.7.0 | playwright/firefox:84.0 | 1.7.0 | 84.0b9
| playwright/firefox:playwright-1.8.0 | - | 1.8.0 | 85.0b5
| playwright/firefox:playwright-1.8.1 | playwright/firefox:85.0 | 1.8.1 | 85.0b5
| playwright/firefox:playwright-1.9.0 | - | 1.9.0 | 86.0b10
| playwright/firefox:playwright-1.9.1 | - | 1.9.1 | 86.0b10
| playwright/firefox:playwright-1.9.2 | playwright/firefox:86.0 | 1.9.2 | 86.0b10
| playwright/firefox:playwright-1.10.0 | playwright/firefox:87.0 | 1.10.0 | 87.0b10
| playwright/firefox:playwright-1.11.0 | - | 1.11.0 | 89.0b6
| playwright/firefox:playwright-1.11.1 | - | 1.11.0 | 89.0b6
| playwright/firefox:playwright-1.12.0 | - | 1.12.0 | 89.0
| playwright/firefox:playwright-1.12.1 | - | 1.12.1 | 89.0
| playwright/firefox:playwright-1.12.2 | - | 1.12.2 | 89.0
| playwright/firefox:playwright-1.12.3 | playwright/firefox:89.0 | 1.12.3 | 89.0
| playwright/firefox:playwright-1.13.0 | - | 1.13.0 | 90.0
| playwright/firefox:playwright-1.13.1 | playwright/firefox:90.0 | 1.13.1 | 90.0
| playwright/firefox:playwright-1.14.0 | - | 1.14.0 | 91.0
| playwright/firefox:playwright-1.14.1 | playwright/firefox:91.0 | 1.14.1 | 91.0
| playwright/firefox:playwright-1.15.0 | - | 1.15.0 | 92.0
| playwright/firefox:playwright-1.15.1 | - | 1.15.1 | 92.0
| playwright/firefox:playwright-1.15.2 | playwright/firefox:92.0 | 1.15.2 | 92.0
| playwright/firefox:playwright-1.16.0 | - | 1.16.0 | 93.0
| playwright/firefox:playwright-1.16.1 | - | 1.16.1 | 93.0
| playwright/firefox:playwright-1.16.2 | - | 1.16.2 | 93.0
| playwright/firefox:playwright-1.16.3 | playwright/firefox:93.0 | 1.16.3 | 93.0
| playwright/firefox:playwright-1.17.0 | - | 1.17.0 | 94.0.1
| playwright/firefox:playwright-1.17.1 | - | 1.17.1 | 94.0.1
| playwright/firefox:playwright-1.17.2 | playwright/firefox:94.0 | 1.17.2 | 94.0.1
| playwright/firefox:playwright-1.18.0 | - | 1.18.0 | 95.0
| playwright/firefox:playwright-1.18.1 | playwright/firefox:95.0 | 1.18.1 | 95.0
| playwright/firefox:playwright-1.19.0 | - | 1.19.0 | 96.0.1
| playwright/firefox:playwright-1.19.1 | - | 1.19.1 | 96.0.1
| playwright/firefox:playwright-1.19.2 | playwright/firefox:96.0 | 1.19.2 | 96.0.1
| playwright/firefox:playwright-1.20.0 | - | 1.20.0 | 97.0.1
| playwright/firefox:playwright-1.20.1 | - | 1.20.1 | 97.0.1
| playwright/firefox:playwright-1.20.2 | playwright/firefox:97.0 | 1.20.2 | 97.0.1
| playwright/firefox:playwright-1.21.0 | - | 1.21.0 | 98.0.2
| playwright/firefox:playwright-1.21.1 | playwright/firefox:98.0 | 1.21.1 | 98.0.2
| playwright/firefox:playwright-1.22.0 | playwright/firefox:99.0 | 1.22.0 | 99.0.1
|===
.Webkit (Safari Desktop) Images
|===
| Image | Aliases | Playwright version | Safari version
| playwright/webkit:playwright-1.1.1 | - | 1.1.1 | 13.2
| playwright/webkit:playwright-1.2.0 | playwright/webkit:13.2 | 1.2.0 | 14.0
| playwright/webkit:playwright-1.3.0 | - | 1.3.0 | 14.0
| playwright/webkit:playwright-1.4.0 | - | 1.4.0 | 14.0
| playwright/webkit:playwright-1.5.1 | - | 1.5.1 | 14.0
| playwright/webkit:playwright-1.6.1 | playwright/webkit:14.0 | 1.6.1 | 14.0
| playwright/webkit:playwright-1.7.0 | - | 1.7.0 | 14.1
| playwright/webkit:playwright-1.8.0 | - | 1.8.0 | 14.1
| playwright/webkit:playwright-1.8.1 | - | 1.8.1 | 14.1
| playwright/webkit:playwright-1.9.0 | - | 1.9.0 | 14.1
| playwright/webkit:playwright-1.9.1 | - | 1.9.1 | 14.1
| playwright/webkit:playwright-1.9.2 | playwright/webkit:14.1 | 1.9.2 | 14.1
| playwright/webkit:playwright-1.10.0 | - | 1.10.0 | 14.2
| playwright/webkit:playwright-1.11.0 | - | 1.11.0 | 14.2
| playwright/webkit:playwright-1.11.1 | - | 1.11.0 | 14.2
| playwright/webkit:playwright-1.12.0 | - | 1.12.0 | 14.2
| playwright/webkit:playwright-1.12.1 | - | 1.12.1 | 14.2
| playwright/webkit:playwright-1.12.2 | - | 1.12.2 | 14.2
| playwright/webkit:playwright-1.12.3 | - | 1.12.3 | 14.2
| playwright/webkit:playwright-1.13.0 | - | 1.13.0 | 14.2
| playwright/webkit:playwright-1.13.1 | playwright/webkit:14.2 | 1.13.1 | 14.2
| playwright/webkit:playwright-1.14.0 | - | 1.14.0 | 15.0
| playwright/webkit:playwright-1.14.1 | - | 1.14.1 | 15.0
| playwright/webkit:playwright-1.15.0 | - | 1.15.0 | 15.0
| playwright/webkit:playwright-1.15.1 | - | 1.15.1 | 15.0
| playwright/webkit:playwright-1.15.2 | playwright/webkit:15.0 | 1.15.2 | 15.0
| playwright/webkit:playwright-1.16.0 | - | 1.16.0 | 15.4
| playwright/webkit:playwright-1.16.1 | - | 1.16.1 | 15.4
| playwright/webkit:playwright-1.16.2 | - | 1.16.2 | 15.4
| playwright/webkit:playwright-1.16.3 | - | 1.16.3 | 15.4
| playwright/webkit:playwright-1.17.0 | - | 1.17.0 | 15.4
| playwright/webkit:playwright-1.17.1 | - | 1.17.1 | 15.4
| playwright/webkit:playwright-1.17.2 | - | 1.17.2 | 15.4
| playwright/webkit:playwright-1.18.0 | - | 1.18.0 | 15.4
| playwright/webkit:playwright-1.18.1 | - | 1.18.1 | 15.4
| playwright/webkit:playwright-1.19.0 | - | 1.19.0 | 15.4
| playwright/webkit:playwright-1.19.1 | - | 1.19.1 | 15.4
| playwright/webkit:playwright-1.19.2 | - | 1.19.2 | 15.4
| playwright/webkit:playwright-1.20.0 | - | 1.20.0 | 15.4
| playwright/webkit:playwright-1.20.1 | - | 1.20.1 | 15.4
| playwright/webkit:playwright-1.20.2 | - | 1.20.2 | 15.4
| playwright/webkit:playwright-1.21.0 | - | 1.21.0 | 15.4
| playwright/webkit:playwright-1.21.1 | - | 1.21.1 | 15.4
| playwright/webkit:playwright-1.22.0 | playwright/webkit:15.4 | 1.22.0 | 15.4
|===
+12
View File
@@ -0,0 +1,12 @@
== Safari
IMPORTANT: Real Safari browser can only run under MacOS \ iOS. These Linux images are using WebKit engine used in Safari browser. From functional point of view real Safari and WebKit browser being used inside these images are equal. However fonts and pixel per pixel rendering could differ.
.Safari Images
|===
| Image | Webkit version
| browsers/safari:13.0 | 605.x
| browsers/safari:14.0 | 610.4.3.1.7
| browsers/safari:15.0 | 613.1.6.1
|===
+4
View File
@@ -0,0 +1,4 @@
== Windows Browsers
We don't build ready to use images for Internet Explorer and any other browser under Windows because of Windows license limitations. However we provide detailed instructions on building such images https://github.com/aerokube/windows-images[here]. The same approach can be used for preparing images with Microsoft Edge, Firefox, Chrome and Opera under Windows.
+20
View File
@@ -0,0 +1,20 @@
module github.com/aerokube/images
go 1.17
require (
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e
github.com/fatih/color v1.9.0 // indirect
github.com/markbates/pkger v0.17.1
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/spf13/cobra v1.1.1
gopkg.in/cheggaaa/pb.v1 v1.0.28
)
require (
github.com/gobuffalo/here v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
)
+317
View File
@@ -0,0 +1,317 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e h1:ogUKYFNcdYUIBSLibE4+EjbTJazoHr5JsWWx21Lpn8c=
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e/go.mod h1:cbmYNkm9xeQlNoWEPtOUcvNok2gSD7ErMnYkRW+eHi8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+11
View File
@@ -0,0 +1,11 @@
package main
import (
"github.com/aerokube/images/cmd"
)
//go:generate pkger -include /static -o build
func main() {
cmd.Execute()
}
+3
View File
@@ -0,0 +1,3 @@
tmp/*
chromedriver*
*.apk
+93
View File
@@ -0,0 +1,93 @@
FROM golang:1.17 as go
COPY tmp/devtools /devtools
RUN \
apt-get update && \
apt-get install -y upx-ucl libx11-dev && \
cd /devtools && \
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \
upx /devtools/devtools
FROM ubuntu:18.04
ARG APPIUM_VERSION="1.8.1"
RUN \
apt update && \
apt remove -y libcurl4 && \
apt install -y apt-transport-https ca-certificates tzdata locales libcurl4 curl gnupg && \
curl --silent --location https://deb.nodesource.com/setup_12.x | bash - && \
apt install -y --no-install-recommends \
curl \
iproute2 \
nodejs \
openjdk-8-jdk-headless \
unzip \
xvfb \
libpulse0 \
libxcomposite1 \
libxcursor1 \
libxi6 \
libasound2 \
fluxbox \
x11vnc \
feh \
wmctrl \
libglib2.0-0 && \
apt-get clean && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
RUN cd / && npm install --prefix ./opt/ appium@$APPIUM_VERSION
COPY android.conf /etc/ld.so.conf.d/
COPY fluxbox/aerokube /usr/share/fluxbox/styles/
COPY fluxbox/init /root/.fluxbox/
COPY fluxbox/aerokube.png /usr/share/images/fluxbox/
COPY --from=go /devtools/devtools /usr/bin/
# Android SDK
ENV ANDROID_HOME /opt/android-sdk-linux
ENV PATH /opt/android-sdk-linux/platform-tools:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/emulator:$PATH
ENV LD_LIBRARY_PATH ${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/gles_swiftshader:${ANDROID_HOME}/emulator/lib64/qt/lib:${ANDROID_HOME}/emulator/lib64/vulkan:${LD_LIBRARY_PATH}
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
ARG ANDROID_DEVICE=""
ARG REPLACE_IMG="y"
ARG AVD_NAME="android6.0-1"
ARG BUILD_TOOLS="build-tools;23.0.1"
ARG PLATFORM="android-23"
ARG EMULATOR_IMAGE="system-images;android-23;default;x86"
ARG EMULATOR_IMAGE_TYPE="default"
ARG ANDROID_ABI="x86"
ARG SDCARD_SIZE="500"
ARG USERDATA_SIZE="500"
RUN \
curl -o sdk-tools.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip && \
mkdir -p /opt/android-sdk-linux /root/.config/Android\ Open\ Source\ Project/ && \
unzip -q sdk-tools.zip -d /opt/android-sdk-linux && \
rm sdk-tools.zip && \
yes | sdkmanager --licenses
RUN \
sdkmanager "emulator" "tools" "platform-tools" "$BUILD_TOOLS" "platforms;$PLATFORM" "$EMULATOR_IMAGE" && \
mksdcard "$SDCARD_SIZE"M sdcard.img && \
echo "no" | ( \
([ -n "$ANDROID_DEVICE" ] && avdmanager create avd -n "$AVD_NAME" -k "$EMULATOR_IMAGE" --abi "$ANDROID_ABI" --device "$ANDROID_DEVICE" --sdcard /sdcard.img ) || \
avdmanager create avd -n "$AVD_NAME" -k "$EMULATOR_IMAGE" --abi "$ANDROID_ABI" --sdcard /sdcard.img \
) && \
ldconfig && \
( \
resize2fs /root/.android/avd/$AVD_NAME.avd/userdata.img "$USERDATA_SIZE"M || \
/opt/android-sdk-linux/emulator/qemu-img resize -f raw /root/.android/avd/$AVD_NAME.avd/userdata.img "$USERDATA_SIZE"M \
) && \
([ -z "$REPLACE_IMG" ] || mv /root/.android/avd/$AVD_NAME.avd/userdata.img /root/.android/avd/$AVD_NAME.avd/userdata-qemu.img ) && \
rm /opt/android-sdk-linux/system-images/$PLATFORM/$EMULATOR_IMAGE_TYPE/"$ANDROID_ABI"/userdata.img
COPY ["Emulator.conf", "/root/.config/Android Open Source Project/Emulator.conf"]
COPY emulator-snapshot.sh tmp/chromedriver* *.apk /usr/bin/
# Entrypoint
COPY tmp/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
+2
View File
@@ -0,0 +1,2 @@
[General]
showNestedWarning=false
+2
View File
@@ -0,0 +1,2 @@
/opt/android-sdk-linux/emulator/lib64
/opt/android-sdk-linux/emulator/lib64/gles_swiftshader
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
MAX_ATTEMPTS=5
adb root
adb devices | grep emulator | cut -f1 | while read id; do
apks=(/usr/bin/*.apk)
for apk in "${apks[@]}"; do
if [ -r "$apk" ]; then
for i in `seq 1 ${MAX_ATTEMPTS}`; do
echo "Installing $apk (attempt #$i of $MAX_ATTEMPTS)"
adb -s "$id" install -r "$apk" && break || sleep 15 && echo "Retrying to install $apk"
done
fi
done
adb -s "$id" emu kill -2 || true
done
rm -f /tmp/.X99-lock || true
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
CHROMEDRIVER_PORT=9515
BOOTSTRAP_PORT=4725
EMULATOR=emulator-5554
APPIUM_ARGS=${APPIUM_ARGS:-""}
EMULATOR_ARGS=${EMULATOR_ARGS:-""}
PORT=${PORT:-"4444"}
DISPLAY_NUM=99
export DISPLAY=":$DISPLAY_NUM"
SCREEN_RESOLUTION=${SCREEN_RESOLUTION:-"1920x1080x24"}
SKIN=${SKIN:-"1080x1920"}
STOP=""
VERBOSE=${VERBOSE:-""}
if [ -z "$VERBOSE" ]; then
if [ -z "$APPIUM_ARGS" ]; then
APPIUM_ARGS="--log-level error"
fi
else
EMULATOR_ARGS="$EMULATOR_ARGS -verbose"
fi
clean() {
STOP="yes"
if [ -n "$APPIUM_PID" ]; then
kill -TERM "$APPIUM_PID"
fi
if [ -n "$EMULATOR_PID" ]; then
kill -TERM "$EMULATOR_PID"
fi
if [ -n "$X11VNC_PID" ]; then
kill -TERM "$X11VNC_PID"
fi
if [ -n "$DEVTOOLS_PID" ]; then
kill -TERM "$DEVTOOLS_PID"
fi
if [ -n "$XVFB_PID" ]; then
kill -TERM "$XVFB_PID"
fi
}
trap clean SIGINT SIGTERM
/usr/bin/xvfb-run -e /dev/stdout -l -n "$DISPLAY_NUM" -s "-ac -screen 0 $SCREEN_RESOLUTION -noreset -listen tcp" /usr/bin/fluxbox -display "$DISPLAY" -log /tmp/fluxbox.log 2>/dev/null &
XVFB_PID=$!
retcode=1
until [ $retcode -eq 0 ] || [ -n "$STOP" ]; do
DISPLAY="$DISPLAY" wmctrl -m >/dev/null 2>&1
retcode=$?
if [ $retcode -ne 0 ]; then
echo Waiting X server...
sleep 0.1
fi
done
if [ -n "$STOP" ]; then exit 0; fi
if [ "$ENABLE_VNC" != "true" ] && [ "$ENABLE_VIDEO" != "true" ]; then
EMULATOR_ARGS="$EMULATOR_ARGS -no-window"
fi
ANDROID_AVD_HOME=/root/.android/avd DISPLAY="$DISPLAY" /opt/android-sdk-linux/emulator/emulator ${EMULATOR_ARGS} -writable-system -no-boot-anim -no-audio -no-jni -avd @AVD_NAME@ -sdcard /sdcard.img -skin "$SKIN" -skindir /opt/android-sdk-linux/platforms/@PLATFORM@/skins/ -gpu swiftshader_indirect -ranchu -qemu -enable-kvm &
EMULATOR_PID=$!
if [ "$ENABLE_VNC" == "true" ]; then
x11vnc -display "$DISPLAY" -passwd selenoid -shared -forever -loop500 -rfbport 5900 -rfbportv6 5900 -logfile /tmp/x11vnc.log &
X11VNC_PID=$!
fi
while [ "$(adb shell getprop sys.boot_completed | tr -d '\r')" != "1" ] && [ -z "$STOP" ] ; do sleep 1; done
if [ -n "$STOP" ]; then exit 0; fi
DEFAULT_CAPABILITIES='"androidNaturalOrientation": true, "deviceName": "android", "platformName": "Android", "noReset": true, "udid":"'$EMULATOR'"'
if [ -n "@CHROME_MOBILE@" ]; then
while ip addr | grep inet | grep -q tentative > /dev/null; do sleep 0.1; done
DEFAULT_CAPABILITIES=$DEFAULT_CAPABILITIES', "chromedriverPort": '$CHROMEDRIVER_PORT
/usr/bin/devtools --android &
DEVTOOLS_PID=$!
fi
if [ -x "/usr/bin/chromedriver" ]; then
DEFAULT_CAPABILITIES=$DEFAULT_CAPABILITIES',"chromedriverExecutable": "/usr/bin/chromedriver"'
fi
/opt/node_modules/.bin/appium -a 0.0.0.0 -p "$PORT" -bp "$BOOTSTRAP_PORT" --log-timestamp --log-no-colors --command-timeout 90 --no-reset ${APPIUM_ARGS} --default-capabilities "{$DEFAULT_CAPABILITIES}" &
APPIUM_PID=$!
wait
+175
View File
@@ -0,0 +1,175 @@
###############################################################################
#
# name: aerokube (based on ubuntu_light)
# made: paultag
# date: 16-10-2010
# http://pault.ag/
#
############################################################## BACKGROUND ######
# background: flat
# background.color: #2c001e
# background.colorTo: #2c001e
background: fullscreen
background.pixmap: /usr/share/images/fluxbox/aerokube.png
############################################################## FONTS ##########
menu.frame.font: Ubuntu-10:bold
menu.title.font: Ubuntu-12:bold
toolbar.clock.font: Ubuntu-10:bold
toolbar.workspace.font: Ubuntu-12:bold
toolbar.iconbar.focused.font: Ubuntu-10:bold
toolbar.iconbar.unfocused.font: Ubuntu-10
window.font: Ubuntu-10
############################################################## MENU ###########
menu.bevelWidth: 1
#menu.itemHeight: 35
#menu.titleHeight: 21
menu.borderColor: #525252
menu.borderWidth: 1
menu.bullet: Triangle
menu.bullet.position: Right
menu.frame.underlineColor: #ffffff
menu.title: flat gradient rectangle
menu.title.justify: center
menu.title.color: #FFFFFF
menu.title.colorTo: #FFFFFF
menu.title.textColor: #333333
menu.frame: flat gradient crossdiagonal
menu.frame.justify: left
menu.frame.color: #FFFFFF
menu.frame.colorTo: #FFFFFF
menu.frame.textColor: #333333
menu.frame.disableColor: #aea79f
menu.hilite: flat gradient rectangle
menu.hilite.color: #aea79f
menu.hilite.colorTo: #aea79f
menu.hilite.textColor: #000000
############################################################## TOOLBAR ########
toolbar.bevelWidth: 0
toolbar.borderWidth: 1
toolbar.borderColor: #aea79f
toolbar.height: 20
toolbar.justify: center
toolbar: flat gradient rectangle
toolbar.pixmap:
toolbar.color: #aea79f
toolbar.colorTo: #aea79f
toolbar.clock: parentrelative
toolbar.clock.justify: center
toolbar.clock.color: #
toolbar.clock.colorTo: #
toolbar.clock.textColor: #333333
toolbar.workspace: parentrelative
toolbar.workspace.justify: Center
toolbar.workspace.color: #
toolbar.workspace.colorTo: #
toolbar.workspace.textColor: #333333
toolbar.button: parentrelative
toolbar.button.color: #333333
toolbar.button.colorTo: #333333
toolbar.button.picColor: #333333
toolbar.button.pressed: parentrelative
toolbar.button.pressed.color: #aea79f
toolbar.button.pressed.colorTo: #aea79f
toolbar.button.pressed.picColor: #000000
toolbar.iconbar.borderWidth: 1
toolbar.iconbar.borderColor: #333333
toolbar.iconbar.empty: parentrelative
toolbar.iconbar.empty.color: #
toolbar.iconbar.empty.colorTo: #
toolbar.iconbar.focused.borderWidth: 1
toolbar.iconbar.focused.borderColor: #333333
toolbar.iconbar.focused: flat gradient rectangle
toolbar.iconbar.focused.color: #FFFFFF
toolbar.iconbar.focused.colorTo: #FFFFFF
toolbar.iconbar.focused.textColor: #333333
toolbar.iconbar.focused.justify: center
toolbar.iconbar.unfocused.borderWidth: 1
toolbar.iconbar.unfocused.borderColor: #525252
toolbar.iconbar.unfocused: flat gradient rectangle
toolbar.iconbar.unfocused.color: #aea79f
toolbar.iconbar.unfocused.colorTo: #aea79f
toolbar.iconbar.unfocused.textColor: #444444
toolbar.iconbar.unfocused.justify: center
############################################################## WINDOW #########
window.roundCorners: TopRight TopLeft
window.bevelWidth: 4
window.shade: false
window.borderWidth: 1
window.borderColor: #333333
window.justify: Center
window.title.height: 21
window.title.focus: flat gradient rectangle
window.title.focus.color: #000000
window.title.focus.colorTo: #333333
window.title.unfocus: flat gradient rectangle
window.title.unfocus.color: #111111
window.title.unfocus.colorTo: #444444
window.label.focus: parentrelative
window.label.focus.color: #
window.label.focus.colorTo: #
window.label.focus.textColor: #FFFFFF
window.label.unfocus: parentrelative
window.label.unfocus.color: #
window.label.unfocus.colorTo: #
window.label.unfocus.textColor: #aea79f
window.button.focus: parentrelative
window.button.focus.color: #
window.button.focus.colorTo: #
window.button.focus.picColor: #FFFFFF
window.button.unfocus: parentrelative
window.button.unfocus.Color: #
window.button.unfocus.ColorTo: #
window.button.unfocus.picColor: #888888
window.button.pressed: parentrelative
window.button.pressed.color: #
window.button.pressed.colorTo: #
window.button.pressed.picColor: #000000
window.handle.focus: flat
window.handle.focus.color: #1f1f1f
window.handle.focus.colorTo: #1f1f1f
window.handle.unfocus: lat
window.handle.unfocus.color: #1f1f1f
window.handle.unfocus.colorTo: #1f1f1f
window.handleWidth: 2
window.grip.focus: flat
window.grip.focus.color: #525252
window.grip.focus.colorTo: #525252
window.grip.unfocus: flat
window.grip.unfocus.color: #1f1f1f
window.grip.unfocus.colorTo: #1f1f1f
###############################################################################
# EOF
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+7
View File
@@ -0,0 +1,7 @@
session.menuFile: ~/.fluxbox/menu
session.keyFile: ~/.fluxbox/keys
session.styleFile: /usr/share/fluxbox/styles/aerokube
session.configVersion: 13
session.screen0.strftimeFormat: %d %b, %a %02k:%M:%S
session.screen0.toolbar.visible: false
session.screen0.toolbar.tools:
+251
View File
@@ -0,0 +1,251 @@
#!/bin/bash
set -e
die(){
echo "$1"
return 1
}
require_command(){
if [ -z "$(command -v $1)" ]; then
die "$1 command required for this script to run"
fi
}
request_answer(){
prompt=$1
default_value=$2
if [ -n "$default_value" ]; then
prompt="$prompt [$default_value]"
fi
read -r -p "$prompt " value
if [ -z "$value" ] && [ -n "$default_value" ]; then
value="$default_value"
fi
echo "$value"
}
validate_android_version(){
version="$1"
type=${2:-"default"}
abi=${3:-"x86"}
avd_name="android$version-1"
build_tools="build-tools;29.0.2"
replace_img="y"
case "$version" in
4.4)
platform="android-19"
emulator_image="system-images;android-19;$type;$abi"
;;
5.0)
platform="android-21"
emulator_image="system-images;android-21;$type;$abi"
;;
5.1)
platform="android-22"
emulator_image="system-images;android-22;$type;$abi"
;;
6.0)
platform="android-23"
emulator_image="system-images;android-23;$type;$abi"
;;
7.0)
platform="android-24"
emulator_image="system-images;android-24;$type;$abi"
;;
7.1)
platform="android-25"
emulator_image="system-images;android-25;$type;$abi"
;;
8.0)
platform="android-26"
emulator_image="system-images;android-26;$type;$abi"
;;
8.1)
platform="android-27"
emulator_image="system-images;android-27;$type;$abi"
;;
9.0)
platform="android-28"
emulator_image="system-images;android-28;$type;$abi"
replace_img=""
;;
10.0)
platform="android-29"
emulator_image="system-images;android-29;$type;$abi"
replace_img=""
;;
11.0)
platform="android-30"
emulator_image="system-images;android-30;$type;$abi"
replace_img=""
;;
12.0)
platform="android-31"
emulator_image="system-images;android-31;$type;$abi"
replace_img=""
;;
*)
echo "Unsupported Android version"
false
;;
esac
}
validate_android_image_type(){
type="$1"
case "$type" in
"default" | "google_apis" | "google_apis_playstore" | "android-wear" | "android-tv" )
;;
*)
echo "Unsupported Android image type"
false
;;
esac
}
validate_android_abi(){
abi="$1"
case "$abi" in
"armeabi-v7a" | "arm64-v8a" | "x86" | "x86_64" )
;;
*)
echo "Unsupported Application Binary Interface"
false
;;
esac
}
download_chromedriver() {
pushd "$TMP_DIR"
wget -O chromedriver.zip http://chromedriver.storage.googleapis.com/$1/chromedriver_linux64.zip
unzip chromedriver.zip
rm chromedriver.zip
popd
}
test_image(){
tests_dir=../../selenoid-container-tests/
if [ -d "$tests_dir" ]; then
echo "Running test suite on image."
docker rm -f selenium || true
docker run -d --privileged --name selenium -p 4445:4444 $1
echo "Waiting for image to start..."
sleep 20
pushd "$tests_dir"
mvn clean test -Dgrid.connection.url="http://localhost:4445/wd/hub" -Dgrid.browser.name=chrome || true
popd
docker rm -f selenium || true
else
echo "Skipping tests as $tests_dir does not exist."
fi
}
require_command "docker"
require_command "sed"
require_command "true"
require_command "false"
require_command "wget"
require_command "unzip"
require_command "cut"
TMP_DIR="android/tmp"
rm -Rf ./"$TMP_DIR" || true
mkdir -p "$TMP_DIR"
cp android/entrypoint.sh "$TMP_DIR/entrypoint.sh"
cp -r ../static/chrome/devtools "$TMP_DIR/devtools"
appium_version=$(request_answer "Specify Appium version:" "1.18.1")
until [ "$?" -ne 0 ]; do
android_image_type=$(request_answer "Specify Android image type (possible values: \"default\", \"google_apis\", \"google_apis_playstore\", \"android-tv\", \"android-wear\"):" "default")
if validate_android_image_type "$android_image_type"; then
break
fi
done
until [ "$?" -ne 0 ]; do
android_abi=$(request_answer "Specify Application Binary Interface (possible values: \"armeabi-v7a\", \"arm64-v8a\", \"x86\", \"x86_64\"):" "x86")
if validate_android_abi "$android_abi"; then
break
fi
done
until [ "$?" -ne 0 ]; do
android_version=$(request_answer "Specify Android version:" "8.1")
if validate_android_version "$android_version" "$android_image_type" "$android_abi"; then
break
fi
done
IFS=';' read -ra emulator_image_info <<< "$emulator_image"
emulator_image_type=${emulator_image_info[2]}
sed -i.bak "s|@AVD_NAME@|$avd_name|g" "$TMP_DIR/entrypoint.sh"
sed -i.bak "s|@PLATFORM@|$platform|g" "$TMP_DIR/entrypoint.sh"
android_device=$(request_answer "Specify device preset name if needed (e.g. \"Nexus 4\"):")
sdcard_size=$(request_answer "Specify SD card size, Mb:" 500)
userdata_size=$(request_answer "Specify userdata.img size, Mb:" 500)
image_name="android"
default_tag="$android_version"
chrome_mobile=$(request_answer "Are you building a Chrome Mobile image (for mobile web testing):" "n")
if [ "y" == "$chrome_mobile" ]; then
sed -i.bak 's|@CHROME_MOBILE@|yes|g' "$TMP_DIR/entrypoint.sh"
image_name="chrome-mobile"
else
sed -i.bak 's|@CHROME_MOBILE@||g' "$TMP_DIR/entrypoint.sh"
fi
chromedriver_version=$(request_answer "Specify Chromedriver version if needed (required for Chrome Mobile):")
if [ -n "$chromedriver_version" ]; then
chrome_major_version="$(cut -d'.' -f1 <<<${chromedriver_version})"
chrome_minor_version="$(cut -d'.' -f2 <<<${chromedriver_version})"
if [ -n "$chrome_major_version" ] && [ -n "$chrome_minor_version" ]; then
default_tag="$chrome_major_version.$chrome_minor_version"
fi
fi
tag=$(request_answer "Specify image tag:" "selenoid/$image_name:$default_tag")
need_quickboot=$(request_answer "Add Android quick boot snapshot?" "y")
if [ -n "$chromedriver_version" ]; then
download_chromedriver "$chromedriver_version"
fi
rm -Rf *.bak || true
set -x
tmp_tag="$tag"_tmp
docker build -t "$tmp_tag" \
--build-arg APPIUM_VERSION="$appium_version" \
--build-arg ANDROID_DEVICE="$android_device" \
--build-arg REPLACE_IMG="$replace_img" \
--build-arg AVD_NAME="$avd_name" \
--build-arg BUILD_TOOLS="$build_tools" \
--build-arg PLATFORM="$platform" \
--build-arg EMULATOR_IMAGE="$emulator_image" \
--build-arg EMULATOR_IMAGE_TYPE="$emulator_image_type" \
--build-arg ANDROID_ABI="$android_abi" \
--build-arg SDCARD_SIZE="$sdcard_size" \
--build-arg USERDATA_SIZE="$userdata_size" android
if [ "$need_quickboot" == "y" ]; then
id=$(docker run -e CHROME_MOBILE="$chrome_mobile" -d --privileged "$tmp_tag")
sleep 60
docker exec "$id" "/usr/bin/emulator-snapshot.sh"
sleep 30 # Wait for snapshot to save
docker commit "$id" "$tag"
docker rm -f "$id" || true
else
docker tag "$tmp_tag" "$tag"
fi
docker rmi -f "$tmp_tag" || true
set +x
if [ "y" == "$chrome_mobile" ]; then
test_image "$tag"
fi
read -r -p "Push?" yn
if [ "$yn" == "y" ]; then
docker push "$tag"
fi
+121
View File
@@ -0,0 +1,121 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
var (
listen string
gracePeriod time.Duration
target string
queue = make(chan struct{}, 1)
)
func init() {
flag.StringVar(&listen, "listen", ":4444", "host and port to listen to")
flag.DurationVar(&gracePeriod, "grace-period", 300*time.Second, "graceful shutdown period")
flag.StringVar(&target, "target", "http://localhost:4723", "target proxy url")
flag.Parse()
}
func signalContext() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
defer cancel()
stop := make(chan os.Signal)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
}()
return ctx, cancel
}
func main() {
ctx, cancel := signalContext()
defer cancel()
err := mainCtx(ctx)
if err != nil {
log.Fatal(err)
}
}
func mux(ctx context.Context, u *url.URL) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/wd/hub/session", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case <-r.Context().Done():
return
case <-ctx.Done():
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
case queue <- struct{}{}:
}
(&httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Scheme, r.URL.Host = u.Scheme, u.Host
},
ModifyResponse: func(resp *http.Response) error {
if resp.StatusCode != http.StatusOK {
<-queue
}
return nil
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
<-queue
log.Printf("proxy to %s: %v", u.String(), err)
http.Error(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
},
}).ServeHTTP(w, r)
}))
mux.HandleFunc("/wd/hub/session/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
(&httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Scheme, r.URL.Host = u.Scheme, u.Host
},
}).ServeHTTP(w, r)
fragments := strings.Split(r.URL.Path, "/")
if r.Method == http.MethodDelete && len(fragments) == 5 {
<-queue
}
}))
return mux
}
func mainCtx(ctx context.Context) error {
target, err := url.Parse(target)
if err != nil {
return fmt.Errorf("parse target url: %v", err)
}
server := &http.Server{
Addr: listen,
Handler: mux(ctx, target),
}
e := make(chan error)
go func() {
e <- server.ListenAndServe()
}()
select {
case err := <-e:
return err
case <-ctx.Done():
}
log.Printf("starting graceful shutdown in %v]", gracePeriod)
shCtx, cancel := context.WithTimeout(context.Background(), gracePeriod)
defer cancel()
if err := server.Shutdown(shCtx); err != nil {
return fmt.Errorf("graceful shutdown: %v", err)
}
log.Printf("stopped")
return nil
}
+79
View File
@@ -0,0 +1,79 @@
FROM golang:1.17 as go
COPY xseld /xseld
COPY fileserver /fileserver
RUN \
apt-get update && \
apt-get install -y upx-ucl libx11-dev && \
cd /xseld && \
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \
upx /xseld/xseld && \
cd /fileserver && \
go test -race && \
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \
upx /fileserver/fileserver
FROM ubuntu:20.04
RUN \
apt update && \
apt remove -y libcurl4 && \
apt install -y apt-transport-https ca-certificates tzdata locales libcurl4 curl gnupg && \
DEBIAN_FRONTEND=noninteractive apt -y upgrade && \
echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections && \
echo 'UTC' | tee /etc/timezone && \
dpkg-reconfigure -f noninteractive tzdata && \
echo "gtk-cursor-blink=0" > /root/.gtkrc-2.0 && \
apt update && \
apt install -y ttf-mscorefonts-installer \
ttf-dejavu-core \
fontconfig \
fontconfig-config \
fonts-dejavu-core \
fonts-liberation \
fonts-ubuntu-font-family-console \
fonts-wqy-zenhei \
fonts-thai-tlwg-ttf \
fonts-ipafont-mincho \
fonts-sahadeva \
fonts-noto-unhinted \
fonts-noto-color-emoji \
libfontconfig1 \
libfontenc1 \
libfreetype6 \
libxfont2 \
libxft2 \
libnss3-tools \
xfonts-base \
xfonts-encodings \
xfonts-utils \
flashplugin-installer \
xvfb \
pulseaudio \
fluxbox \
x11vnc \
feh \
wmctrl \
libnss-wrapper \
xsel && \
mkdir -p /var/lib/locales/supported.d/ && grep UTF-8 /usr/share/i18n/SUPPORTED > /var/lib/locales/supported.d/all && \
locale-gen && update-locale && \
fc-cache -f -v && \
adduser --system --home /home/selenium --uid 4096 \
--ingroup root --disabled-password --shell /bin/bash selenium && \
mkdir -p /home/selenium/Downloads && \
mkdir -p /home/selenium/.fluxbox && \
chgrp -R 0 /home/selenium && \
chmod -R g=u /home/selenium && \
ln -sf /bin/true /usr/bin/xdg-open && \
apt-get clean && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
COPY fluxbox /usr/share/fluxbox/styles/
COPY --chown=selenium:root fluxbox /home/selenium/.fluxbox/
COPY aerokube.png /usr/share/images/fluxbox/
COPY --from=go /fileserver/fileserver /usr/bin/
COPY --from=go /xseld/xseld /usr/bin/
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+5
View File
@@ -0,0 +1,5 @@
module fileserver
go 1.17
require github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e
+2
View File
@@ -0,0 +1,2 @@
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e h1:ogUKYFNcdYUIBSLibE4+EjbTJazoHr5JsWWx21Lpn8c=
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e/go.mod h1:cbmYNkm9xeQlNoWEPtOUcvNok2gSD7ErMnYkRW+eHi8=
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
)
func main() {
dir, err := downloadsDir()
if err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", mux(dir)))
}
func downloadsDir() (string, error) {
homeDir := os.Getenv("HOME")
if homeDir == "" {
homeDir = "/home/selenium"
}
dir := filepath.Join(homeDir, "Downloads")
err := os.MkdirAll(dir, 0755)
if err != nil {
return "", fmt.Errorf("failed to create downloads dir: %v", err)
}
return dir, nil
}
const jsonParam = "json"
func mux(dir string) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodDelete {
deleteFileIfExists(w, r, dir)
return
}
if _, ok := r.URL.Query()[jsonParam]; ok {
listFilesAsJson(w, dir)
return
}
http.FileServer(http.Dir(dir)).ServeHTTP(w, r)
})
return mux
}
func listFilesAsJson(w http.ResponseWriter, dir string) {
files, err := ioutil.ReadDir(dir)
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().After(files[j].ModTime())
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ret := []string{}
for _, f := range files {
ret = append(ret, f.Name())
}
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(ret)
}
func deleteFileIfExists(w http.ResponseWriter, r *http.Request, dir string) {
fileName := strings.TrimPrefix(r.URL.Path, "/")
filePath := filepath.Join(dir, fileName)
_, err := os.Stat(filePath)
if err != nil {
http.Error(w, fmt.Sprintf("Unknown file %s", fileName), http.StatusNotFound)
return
}
err = os.Remove(filePath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to delete file %s: %v", fileName, err), http.StatusInternalServerError)
return
}
}
+58
View File
@@ -0,0 +1,58 @@
package main
import (
. "github.com/aandryashin/matchers"
. "github.com/aandryashin/matchers/httpresp"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)
var (
dir string
srv *httptest.Server
)
func init() {
dir, _ = ioutil.TempDir("", "fileserver")
srv = httptest.NewServer(mux(dir))
}
func withUrl(path string) string {
return srv.URL + path
}
func TestDownloadAndRemoveFile(t *testing.T) {
tempFile, _ := ioutil.TempFile(dir, "fileserver")
ioutil.WriteFile(tempFile.Name(), []byte("test-data"), 0644)
tempFileName := filepath.Base(tempFile.Name())
resp, err := http.Get(withUrl("/" + tempFileName))
AssertThat(t, err, Is{nil})
AssertThat(t, resp, Code{200})
_, err = os.Stat(tempFile.Name())
AssertThat(t, err, Is{nil})
rsp, err := http.Get(withUrl("/?json"))
AssertThat(t, err, Is{nil})
AssertThat(t, rsp, Code{http.StatusOK})
var files []string
AssertThat(t, rsp, IsJson{&files})
AssertThat(t, files, EqualTo{[]string{tempFileName}})
req, _ := http.NewRequest(http.MethodDelete, withUrl("/" + tempFileName), nil)
resp, err = http.DefaultClient.Do(req)
AssertThat(t, err, Is{nil})
AssertThat(t, resp, Code{200})
_, err = os.Stat(tempFile.Name())
AssertThat(t, err, Not{nil})
}
func TestRemoveMissingFile(t *testing.T) {
req, _ := http.NewRequest(http.MethodDelete, withUrl("/missing-file"), nil)
resp, err := http.DefaultClient.Do(req)
AssertThat(t, err, Is{nil})
AssertThat(t, resp, Code{404})
}
+175
View File
@@ -0,0 +1,175 @@
###############################################################################
#
# name: aerokube (based on ubuntu_light)
# made: paultag
# date: 16-10-2010
# http://pault.ag/
#
############################################################## BACKGROUND ######
# background: flat
# background.color: #2c001e
# background.colorTo: #2c001e
background: fullscreen
background.pixmap: /usr/share/images/fluxbox/aerokube.png
############################################################## FONTS ##########
menu.frame.font: Ubuntu-10:bold
menu.title.font: Ubuntu-12:bold
toolbar.clock.font: Ubuntu-10:bold
toolbar.workspace.font: Ubuntu-12:bold
toolbar.iconbar.focused.font: Ubuntu-10:bold
toolbar.iconbar.unfocused.font: Ubuntu-10
window.font: Ubuntu-10
############################################################## MENU ###########
menu.bevelWidth: 1
#menu.itemHeight: 35
#menu.titleHeight: 21
menu.borderColor: #525252
menu.borderWidth: 1
menu.bullet: Triangle
menu.bullet.position: Right
menu.frame.underlineColor: #ffffff
menu.title: flat gradient rectangle
menu.title.justify: center
menu.title.color: #FFFFFF
menu.title.colorTo: #FFFFFF
menu.title.textColor: #333333
menu.frame: flat gradient crossdiagonal
menu.frame.justify: left
menu.frame.color: #FFFFFF
menu.frame.colorTo: #FFFFFF
menu.frame.textColor: #333333
menu.frame.disableColor: #aea79f
menu.hilite: flat gradient rectangle
menu.hilite.color: #aea79f
menu.hilite.colorTo: #aea79f
menu.hilite.textColor: #000000
############################################################## TOOLBAR ########
toolbar.bevelWidth: 0
toolbar.borderWidth: 1
toolbar.borderColor: #aea79f
toolbar.height: 20
toolbar.justify: center
toolbar: flat gradient rectangle
toolbar.pixmap:
toolbar.color: #aea79f
toolbar.colorTo: #aea79f
toolbar.clock: parentrelative
toolbar.clock.justify: center
toolbar.clock.color: #
toolbar.clock.colorTo: #
toolbar.clock.textColor: #333333
toolbar.workspace: parentrelative
toolbar.workspace.justify: Center
toolbar.workspace.color: #
toolbar.workspace.colorTo: #
toolbar.workspace.textColor: #333333
toolbar.button: parentrelative
toolbar.button.color: #333333
toolbar.button.colorTo: #333333
toolbar.button.picColor: #333333
toolbar.button.pressed: parentrelative
toolbar.button.pressed.color: #aea79f
toolbar.button.pressed.colorTo: #aea79f
toolbar.button.pressed.picColor: #000000
toolbar.iconbar.borderWidth: 1
toolbar.iconbar.borderColor: #333333
toolbar.iconbar.empty: parentrelative
toolbar.iconbar.empty.color: #
toolbar.iconbar.empty.colorTo: #
toolbar.iconbar.focused.borderWidth: 1
toolbar.iconbar.focused.borderColor: #333333
toolbar.iconbar.focused: flat gradient rectangle
toolbar.iconbar.focused.color: #FFFFFF
toolbar.iconbar.focused.colorTo: #FFFFFF
toolbar.iconbar.focused.textColor: #333333
toolbar.iconbar.focused.justify: center
toolbar.iconbar.unfocused.borderWidth: 1
toolbar.iconbar.unfocused.borderColor: #525252
toolbar.iconbar.unfocused: flat gradient rectangle
toolbar.iconbar.unfocused.color: #aea79f
toolbar.iconbar.unfocused.colorTo: #aea79f
toolbar.iconbar.unfocused.textColor: #444444
toolbar.iconbar.unfocused.justify: center
############################################################## WINDOW #########
window.roundCorners: TopRight TopLeft
window.bevelWidth: 4
window.shade: false
window.borderWidth: 1
window.borderColor: #333333
window.justify: Center
window.title.height: 21
window.title.focus: flat gradient rectangle
window.title.focus.color: #000000
window.title.focus.colorTo: #333333
window.title.unfocus: flat gradient rectangle
window.title.unfocus.color: #111111
window.title.unfocus.colorTo: #444444
window.label.focus: parentrelative
window.label.focus.color: #
window.label.focus.colorTo: #
window.label.focus.textColor: #FFFFFF
window.label.unfocus: parentrelative
window.label.unfocus.color: #
window.label.unfocus.colorTo: #
window.label.unfocus.textColor: #aea79f
window.button.focus: parentrelative
window.button.focus.color: #
window.button.focus.colorTo: #
window.button.focus.picColor: #FFFFFF
window.button.unfocus: parentrelative
window.button.unfocus.Color: #
window.button.unfocus.ColorTo: #
window.button.unfocus.picColor: #888888
window.button.pressed: parentrelative
window.button.pressed.color: #
window.button.pressed.colorTo: #
window.button.pressed.picColor: #000000
window.handle.focus: flat
window.handle.focus.color: #1f1f1f
window.handle.focus.colorTo: #1f1f1f
window.handle.unfocus: lat
window.handle.unfocus.color: #1f1f1f
window.handle.unfocus.colorTo: #1f1f1f
window.handleWidth: 2
window.grip.focus: flat
window.grip.focus.color: #525252
window.grip.focus.colorTo: #525252
window.grip.unfocus: flat
window.grip.unfocus.color: #1f1f1f
window.grip.unfocus.colorTo: #1f1f1f
###############################################################################
# EOF
+7
View File
@@ -0,0 +1,7 @@
session.menuFile: ~/.fluxbox/menu
session.keyFile: ~/.fluxbox/keys
session.styleFile: /usr/share/fluxbox/styles/aerokube
session.configVersion: 13
session.screen0.strftimeFormat: %d %b, %a %02k:%M:%S
session.screen0.toolbar.visible: false
session.screen0.toolbar.tools:
+3
View File
@@ -0,0 +1,3 @@
module xseld
go 1.17
+44
View File
@@ -0,0 +1,44 @@
package main
import (
"io"
"log"
"net/http"
"os/exec"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("xsel", "-b")
switch r.Method {
case http.MethodGet:
cmd.Args = append(cmd.Args, "-o")
stdout, err := cmd.StdoutPipe()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go io.Copy(w, stdout)
case http.MethodPost:
cmd.Args = append(cmd.Args, "-i")
stdin, err := cmd.StdinPipe()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go func() {
defer stdin.Close()
io.Copy(stdin, r.Body)
}()
default:
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
err := cmd.Run()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
log.Fatal(http.ListenAndServe(":9090", nil))
}
+28
View File
@@ -0,0 +1,28 @@
FROM golang:1.17 as go
COPY choose /choose
COPY rproxy /rproxy
RUN \
apt-get update && \
apt-get install -y upx-ucl && \
cd /choose && \
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \
upx /choose/choose && \
cd /rproxy && \
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \
upx /rproxy/rproxy
FROM ubuntu:18.04
COPY --from=go /choose/choose /usr/local/bin/
COPY --from=go /rproxy/rproxy /usr/local/bin/
RUN \
apt update && \
apt -y install xvfb x11-utils x11vnc tigervnc-common tigervnc-viewer
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
+16
View File
@@ -0,0 +1,16 @@
#browsers.json
```
"MicrosoftEdge": {
"default": "77",
"versions": {
"77": {
"image": "selenoid/external-host:1.0.0",
"port": "4444",
"path": "/",
"env": ["URLS=[\"http://172.17.0.1:4444/\"]","VNC_PASSWORD=longpassword","SCREEN_RESOLUTION=1921x1080x24"]
}
}
}
```
+30
View File
@@ -0,0 +1,30 @@
package main
import (
"os"
"time"
"fmt"
"encoding/json"
"math/rand"
)
var (
s rand.Source
r *rand.Rand
)
func init() {
s = rand.NewSource(time.Now().Unix())
r = rand.New(s)
}
func main() {
var hosts []string
if err := json.Unmarshal([]byte(os.Getenv("URLS")), &hosts); err != nil {
fmt.Printf("unmarshal json: %v", err)
os.Exit(1)
}
if l := len(hosts); l > 0 {
fmt.Println(hosts[r.Intn(l)])
}
}
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
SCREEN_RESOLUTION=${SCREEN_RESOLUTION:-"1920x1080x24"}
DISPLAY_NUM=99
URLS=${URLS:-'["http://172.17.0.1:4444/"]'}
VNC_PASSWORD=${VNC_PASSWORD:-"password"}
export URLS
export VNC_PASSWORD
export DISPLAY=":$DISPLAY_NUM"
clean() {
if [ -n "$XVFB_PID" ]; then
kill -TERM "$XVFB_PID"
fi
if [ -n "$X11VNC_PID" ]; then
kill -TERM "$X11VNC_PID"
fi
if [ -n "$RPROXY_PID" ]; then
kill -TERM "$RPROXY_PID"
fi
}
trap clean SIGINT SIGTERM
URL=$(choose $URL)
HOST=$(echo $URL | sed -e 's@\(.*\)://@@' -e 's@[:/]\(.*\)$@@')
/bin/bash -c 'echo -e "$VNC_PASSWORD\n$VNC_PASSWORD\nn\n" | vncpasswd file.passwd'>/dev/null
xvfb-run -l -n $DISPLAY_NUM -s "-ac -screen 0 $SCREEN_RESOLUTION -noreset -listen tcp" \
vncviewer passwd=file.passwd $HOST &
XVFB_PID=$!
retcode=1
until [ $retcode -eq 0 ]; do
xdpyinfo -display $DISPLAY >/dev/null 2>&1
retcode=$?
if [ $retcode -ne 0 ]; then
echo Waiting xvfb...
sleep 1
fi
done
x11vnc -display $DISPLAY -passwd selenoid -shared -forever -loop500 -rfbport 5900 -rfbportv6 5900 -logfile /dev/null &
X11VNC_PID=$!
rproxy $URL &
RPROXY_PID=$!
wait
+17
View File
@@ -0,0 +1,17 @@
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
)
func main() {
u, err := url.Parse(os.Args[1])
if err != nil {
log.Fatalf("parse url: %v", err)
}
log.Fatal(http.ListenAndServe(":4444", httputil.NewSingleHostReverseProxy(u)))
}
+16
View File
@@ -0,0 +1,16 @@
FROM browsers/base:7.3.6
RUN \
curl -s https://deb.opera.com/archive.key | apt-key add - && \
echo 'deb https://deb.opera.com/opera/ stable non-free' >> /etc/apt/sources.list.d/opera.list && \
apt-get update && \
apt-get -y install opera=12.16.1860 openjdk-8-jre-headless && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
COPY selenium-server-standalone.jar /usr/share/selenium/
COPY entrypoint.sh /
USER selenium
EXPOSE 4444
ENTRYPOINT ["/entrypoint.sh"]
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
SCREEN_RESOLUTION=${SCREEN_RESOLUTION:-"1920x1080x24"}
ENABLE_WINDOW_MANAGER=${ENABLE_WINDOW_MANAGER:-""}
DISPLAY_NUM=99
export DISPLAY=":$DISPLAY_NUM"
clean() {
if [ -n "$FILESERVER_PID" ]; then
kill -TERM "$FILESERVER_PID"
fi
if [ -n "$XSELD_PID" ]; then
kill -TERM "$XSELD_PID"
fi
if [ -n "$XVFB_PID" ]; then
kill -TERM "$XVFB_PID"
fi
if [ -n "$SELENIUM_PID" ]; then
kill -TERM "$SELENIUM_PID"
fi
if [ -n "$X11VNC_PID" ]; then
kill -TERM "$X11VNC_PID"
fi
}
trap clean SIGINT SIGTERM
/usr/bin/fileserver &
FILESERVER_PID=$!
DISPLAY="$DISPLAY" /usr/bin/xseld &
XSELD_PID=$!
/usr/bin/xvfb-run -l -n "$DISPLAY_NUM" -s "-ac -screen 0 $SCREEN_RESOLUTION -noreset -listen tcp" /usr/bin/fluxbox -display "$DISPLAY" -log /tmp/fluxbox.log 2>/dev/null &
XVFB_PID=$!
retcode=1
until [ $retcode -eq 0 ]; do
DISPLAY="$DISPLAY" wmctrl -m >/dev/null 2>&1
retcode=$?
if [ $retcode -ne 0 ]; then
echo Waiting X server...
sleep 0.1
fi
done
if [ "$ENABLE_VNC" == "true" ]; then
x11vnc -display "$DISPLAY" -passwd selenoid -shared -forever -loop500 -rfbport 5900 -rfbportv6 5900 -logfile /tmp/x11vnc.log &
X11VNC_PID=$!
fi
/usr/bin/java -Xmx256m -Djava.security.egd=file:/dev/./urandom -jar /usr/share/selenium/selenium-server-standalone.jar -port 4444 -browserTimeout 120 &
SELENIUM_PID=$!
wait
+3
View File
@@ -0,0 +1,3 @@
FROM wernight/phantomjs:2.1.1
EXPOSE 4444
CMD ["/usr/local/bin/phantomjs", "--webdriver=4444"]
+10
View File
@@ -0,0 +1,10 @@
# Rebuilding Debian Packages
1. Download package from APT-repository:
```
$ apt-get download google-chrome-stable=48.0.2564.109-1+suffix0
```
2. Rebuild package to new owner using script:
```
$ rebuild-deb.sh -f google-chrome-stable=48.0.2564.109-1+suffix0_amd64.deb -r 'suffix0' -a 'aerokube0'
```
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
set -e
usage()
{
cat << EOF
usage: $0 options
OPTIONS:
-h Show this message
-f Filename
-r Remove version postfix
-a Add version postfix
EOF
}
filename=''
remove_postfix=''
add_postfix=''
while getopts 'f:r:a:' OPTION
do
case $OPTION in
h)
usage
exit 1
;;
f)
filename=$OPTARG
;;
r)
remove_postfix=$OPTARG
;;
a)
add_postfix=$OPTARG
;;
?)
usage
exit
;;
esac
done
if [ -z "$filename" ]; then
usage
exit 1
fi
set -x
cp "$filename" .
spackage=$(echo "$filename" | awk -F '/' '{print $NF}')
package=$(echo "$spackage" | awk -F '_' '{print $1}')
version=$(echo "$spackage" | awk -F '_' '{print $2}')
rm -Rf "$package" control changelog
# Set variables
export DH_ALWAYS_EXCLUDE="CVS:.svn:.git"
# Extract package content
dpkg-deb -x $spackage $package/
# Extract package control
dpkg-deb -e $spackage $package/DEBIAN
# Extract package changelog
changelog=$(find $package/usr/share/doc -name 'changelog*.gz' 2>/dev/null| head -n 1)
if [ -n "$changelog" -a -f "$changelog" ]; then
rm -f "$changelog"
fi
old_version=$version
if [ -n "$remove_postfix" ]; then
version=$(echo $version | sed -e "s|+$remove_postfix||g")
old_version=$version"+"$remove_postfix
fi
new_version=$version
if [ -n "$add_postfix" ]; then
new_version=$new_version"+"$add_postfix
fi
dch --create --force-distribution --distribution unstable --package "$package" --newversion "$new_version" -c changelog 'Package rebuilt'
# Fix control
section=$(awk '/Section:/ {print $NF}' $package/DEBIAN/control 2>/dev/null || echo 'misc')
priority=$(awk '/Priority:/ {print $NF}' $package/DEBIAN/control 2>/dev/null || echo 'extra')
echo "$(echo $spackage | sed "s/$old_version/$new_version/") $section $priority" > files
echo "Source: $package" > control
grep -iv "Source:" $package/DEBIAN/control >> control
cp control $package/DEBIAN/control
# Build package
sed -i "s/$old_version/$new_version/g" $package/DEBIAN/control
dpkg-deb -b $package/ ./
# Remove source package
rm -f "./$spackage"
+76
View File
@@ -0,0 +1,76 @@
# Contributor: Anton Dzyk <anton.dzyk@yandex.ru>
# Maintainer: Ivan Krutov <vania-pooh@aerokube.com>
pkgname=ffmpeg-x11grab
pkgsource=ffmpeg
pkgver=4.3
pkgrel=1
pkgdesc="FFMPEG version optimized for x11grab to x264"
url="https://ffmpeg.org"
arch="all"
license="LGPL-2.1-or-later GPL-2.0-or-later"
makedepends="gnutls-dev lame-dev libvorbis-dev xvidcore-dev zlib-dev libvdpau-dev
imlib2-dev x264-dev libtheora-dev coreutils bzip2-dev perl-dev libvpx-dev
libvpx-dev sdl2-dev libxfixes-dev libva-dev v4l-utils-dev yasm opus-dev libass-dev pulseaudio-dev"
source="https://ffmpeg.org/releases/ffmpeg-$pkgver.tar.xz
0001-libavutil-clean-up-unused-FF_SYMVER-macro.patch
"
builddir="$srcdir/$pkgsource-$pkgver"
build() {
local _dbg="--disable-debug"
local _asm=""
[ -n "$DEBUG" ] && _dbg="--enable-debug"
case "$CARCH" in
x86 | arm*) _asm="--disable-asm" ;;
esac
cd "$builddir"
./configure \
--prefix=/usr \
--enable-avfilter \
--enable-gpl \
--enable-libx264 \
--enable-libpulse \
--enable-static \
--enable-small \
--disable-ffplay \
--disable-ffprobe \
--disable-doc \
--disable-htmlpages \
--disable-manpages \
--disable-podpages \
--disable-txtpages \
--disable-w32threads \
--disable-alsa \
--disable-audiotoolbox \
--disable-cuda \
--disable-cuvid \
--disable-d3d11va \
--disable-dxva2 \
--disable-nvenc \
--disable-vaapi \
--disable-vdpau \
--disable-videotoolbox \
--disable-librtmp \
--disable-devices \
--enable-indev=xcbgrab \
--enable-indev=pulse \
$_asm $_dbg
make
}
package() {
cd "$builddir"
make DESTDIR="$pkgdir" install
}
libs() {
pkgdesc="Libraries for ffmpeg"
replaces="ffmpeg"
mkdir -p "$subpkgdir"/usr
mv "$pkgdir"/usr/lib "$subpkgdir"/usr
}
sha512sums="f031eb6c4423887af323ab7d1f431234d4e30993a52db45dccf427b41eb442a3bd020dcbc13e83cbf813fad0f36c849cb651203570148387c864507aa19f313a ffmpeg-4.3.tar.xz
1047a23eda51b576ac200d5106a1cd318d1d5291643b3a69e025c0a7b6f3dbc9f6eb0e1e6faa231b7e38c8dd4e49a54f7431f87a93664da35825cc2e9e8aedf4 0001-libavutil-clean-up-unused-FF_SYMVER-macro.patch"
+21
View File
@@ -0,0 +1,21 @@
FROM alpine:3.12
RUN apk add -U alpine-sdk && adduser -g "" -D -G abuild selenoid
USER selenoid
RUN git clone git://git.alpinelinux.org/aports /home/selenoid/aports
ADD APKBUILD /home/selenoid/aports/community/ffmpeg/
RUN cd /home/selenoid/aports/community/ffmpeg/ && \
abuild checksum && \
abuild unpack && \
abuild deps && \
abuild build
USER root
FROM alpine:3.12
RUN apk add -U x264-libs sdl2 libxcb libbz2 xset pulseaudio-utils && \
rm -rf /var/cache/apk/*
COPY --from=0 /home/selenoid/aports/community/ffmpeg/src/ffmpeg-4.3/ffmpeg /usr/bin/ffmpeg
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
+42
View File
@@ -0,0 +1,42 @@
#!/bin/sh
VIDEO_SIZE=${VIDEO_SIZE:-"1920x1080"}
BROWSER_CONTAINER_NAME=${BROWSER_CONTAINER_NAME:-"browser"}
DISPLAY=${DISPLAY:-"99"}
FILE_NAME=${FILE_NAME:-"video.mp4"}
FRAME_RATE=${FRAME_RATE:-"12"}
CODEC=${CODEC:-"libx264"}
PRESET=${PRESET:-""}
if [ "$CODEC" == "libx264" -a -n "$PRESET" ]; then
PRESET="-preset $PRESET"
fi
INPUT_OPTIONS=${INPUT_OPTIONS:-""}
HIDE_CURSOR=${HIDE_CURSOR:-""}
if [ -n "$HIDE_CURSOR" ]; then
INPUT_OPTIONS="$INPUT_OPTIONS -draw_mouse 0"
fi
retcode=1
max_attempts=300
attempts=0
echo 'Waiting for display to open...'
until [ $retcode -eq 0 -o $attempts -eq $max_attempts ]; do
xset -display ${BROWSER_CONTAINER_NAME}:${DISPLAY} b off > /dev/null 2>&1
retcode=$?
if [ $retcode -ne 0 ]; then
echo 'Sleeping before next attempt...'
sleep 0.1
fi
attempts=$((attempts+1))
done
mkdir -p ~/.config/pulse
echo -n 'gIvST5iz2S0J1+JlXC1lD3HWvg61vDTV1xbmiGxZnjB6E3psXsjWUVQS4SRrch6rygQgtpw7qmghDFTaekt8qWiCjGvB0LNzQbvhfs1SFYDMakmIXuoqYoWFqTJ+GOXYByxpgCMylMKwpOoANEDePUCj36nwGaJNTNSjL8WBv+Bf3rJXqWnJ/43a0hUhmBBt28Dhiz6Yqowa83Y4iDRNJbxih6rB1vRNDKqRr/J9XJV+dOlM0dI+K6Vf5Ag+2LGZ3rc5sPVqgHgKK0mcNcsn+yCmO+XLQHD1K+QgL8RITs7nNeF1ikYPVgEYnc0CGzHTMvFR7JLgwL2gTXulCdwPbg=='| base64 -d>~/.config/pulse/cookie
export PULSE_SERVER=${BROWSER_CONTAINER_NAME}
if pactl info >/dev/null 2>&1; then
exec ffmpeg -f pulse -i default -y -f x11grab -video_size ${VIDEO_SIZE} -r ${FRAME_RATE} ${INPUT_OPTIONS} -i ${BROWSER_CONTAINER_NAME}:${DISPLAY} -codec:v ${CODEC} ${PRESET} -pix_fmt yuv420p -filter:v "pad=ceil(iw/2)*2:ceil(ih/2)*2" "/data/$FILE_NAME"
else
exec ffmpeg -y -f x11grab -video_size ${VIDEO_SIZE} -r ${FRAME_RATE} ${INPUT_OPTIONS} -i ${BROWSER_CONTAINER_NAME}:${DISPLAY} -codec:v ${CODEC} ${PRESET} -pix_fmt yuv420p -filter:v "pad=ceil(iw/2)*2:ceil(ih/2)*2" "/data/$FILE_NAME"
fi
+25
View File
@@ -0,0 +1,25 @@
ARG VERSION
FROM golang:1.17 as go
COPY devtools /devtools
RUN \
apt-get update && \
apt-get install -y upx-ucl && \
cd /devtools && \
go test -race && \
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" && \
upx /devtools/devtools
FROM selenoid/dev_chrome:$VERSION
ENV DBUS_SESSION_BUS_ADDRESS=/dev/null
COPY --from=go /devtools/devtools /usr/bin/
COPY chromedriver /usr/bin/
COPY entrypoint.sh /
RUN chmod +x /usr/bin/chromedriver
USER selenium
EXPOSE 4444
ENTRYPOINT ["/entrypoint.sh"]
+18
View File
@@ -0,0 +1,18 @@
FROM browsers/base:7.3.6
ARG VERSION
ARG PACKAGE=google-chrome-stable
ARG INSTALL_DIR=chrome
LABEL browser=$PACKAGE:$VERSION
RUN \
curl -s https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
echo 'deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main' > /etc/apt/sources.list.d/google.list && \
apt-get update && \
apt-get -y --no-install-recommends install iproute2 $PACKAGE=$VERSION && \
sed -i -e 's@exec -a "$0" "$HERE/chrome"@& --no-sandbox --disable-gpu@' /opt/google/$INSTALL_DIR/google-chrome && \
chown root:root /opt/google/$INSTALL_DIR/chrome-sandbox && \
chmod 4755 /opt/google/$INSTALL_DIR/chrome-sandbox && \
google-chrome --version && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
+1
View File
@@ -0,0 +1 @@
devtools
+187
View File
@@ -0,0 +1,187 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/mafredri/cdp/devtool"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
const (
devtoolsBaseDir = "/tmp"
slash = "/"
)
var (
defaultDevtoolsHost = "127.0.0.1:9222"
)
func root() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/browser", browser)
mux.HandleFunc("/json/protocol", protocol)
mux.HandleFunc("/page", page)
mux.HandleFunc("/page/", page)
mux.HandleFunc("/", browser)
return mux
}
func browser(w http.ResponseWriter, r *http.Request) {
u, err := getBrowserWebSocketUrl()
if err != nil {
log.Printf("[BROWSER_URL_ERROR] [%v]", err)
return
}
log.Printf("[BROWSER] [%s]", u.String())
proxyWebSocket(w, r, u)
}
func proxyWebSocket(w http.ResponseWriter, r *http.Request, u *url.URL) {
u.Scheme = "http"
(&httputil.ReverseProxy{
Director: func(r *http.Request) {
r.Host = "localhost"
r.URL = u
},
}).ServeHTTP(w, r)
}
func page(w http.ResponseWriter, r *http.Request) {
fragments := strings.Split(r.URL.Path, slash)
targetId := ""
if len(fragments) == 3 {
targetId = fragments[2]
}
u, err := getPageWebSocketUrl(targetId)
if err != nil {
log.Printf("[PAGE_URL_ERROR] [%v]", err)
return
}
log.Printf("[PAGE] [%s]", u.String())
proxyWebSocket(w, r, u)
}
func protocol(w http.ResponseWriter, r *http.Request) {
h, err := devtoolsHost()
if err != nil {
log.Printf("[DEVTOOLS_HOST_ERROR] [%v]", err)
http.Error(w, fmt.Sprintf("Failed to detect devtools host: %v", err), http.StatusInternalServerError)
return
}
u := &url.URL{
Host: h,
Scheme: "http",
Path: "/json/protocol",
}
log.Printf("[PROTOCOL] [%s]", u.String())
(&httputil.ReverseProxy{
Director: func(r *http.Request) {
r.Host = "localhost"
r.URL = u
},
}).ServeHTTP(w, r)
}
func getBrowserWebSocketUrl() (*url.URL, error) {
ctx := context.Background()
h, err := devtoolsHost()
if err != nil {
return nil, fmt.Errorf("failed to detect devtools port: %v", err)
}
dt := devtool.New(fmt.Sprintf("http://%s", h))
ver, err := dt.Version(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get browser websocket url: %v", err)
}
wsUrl, err := url.Parse(ver.WebSocketDebuggerURL)
if err == nil {
return wsUrl, nil
}
return nil, errors.New("browser websocket URL information not found")
}
func getPageWebSocketUrl(targetId string) (*url.URL, error) {
ctx := context.Background()
h, err := devtoolsHost()
if err != nil {
return nil, fmt.Errorf("failed to detect devtools port: %v", err)
}
dt := devtool.New(fmt.Sprintf("http://%s", h))
targets, err := dt.List(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list targets: %v", err)
}
for _, t := range targets {
if (targetId == "" && t.Type == devtool.Page) || targetId == t.ID {
wsUrl, err := url.Parse(t.WebSocketDebuggerURL)
if err != nil {
return nil, fmt.Errorf("invalid websocket URL for matched target %s: %v", t.ID, err)
}
return wsUrl, nil
}
}
return nil, errors.New("no matching target found")
}
func devtoolsHost() (string, error) {
if android {
return androidDevtoolsHost()
}
return detectDevtoolsHost(devtoolsBaseDir), nil
}
func androidDevtoolsHost() (string, error) {
const androidDevtoolsPort = 9333
cmd := exec.Command("adb", "forward", fmt.Sprintf("tcp:%d", androidDevtoolsPort), "localabstract:chrome_devtools_remote")
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("failed to forward devtools port: %v", err)
}
return fmt.Sprintf("localhost:%d", androidDevtoolsPort), nil
}
func detectDevtoolsHost(baseDir string) string {
var candidates []string
for _, glob := range []string{".com.google.Chrome*", ".org.chromium.Chromium*"} {
cds, err := filepath.Glob(filepath.Join(baseDir, glob))
if err == nil {
candidates = append(candidates, cds...)
}
}
for _, c := range candidates {
f, err := os.Stat(c)
if err != nil {
continue
}
if !f.IsDir() {
continue
}
portFile := filepath.Join(c, "DevToolsActivePort")
data, err := ioutil.ReadFile(portFile)
if err != nil {
continue
}
lines := strings.Split(string(data), "\n")
if len(lines) == 0 {
continue
}
port, err := strconv.Atoi(lines[0])
if err != nil {
continue
}
return fmt.Sprintf("127.0.0.1:%d", port)
}
return defaultDevtoolsHost
}
+176
View File
@@ -0,0 +1,176 @@
package main
import (
"context"
"encoding/json"
"fmt"
. "github.com/aandryashin/matchers"
. "github.com/aandryashin/matchers/httpresp"
"github.com/gorilla/websocket"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/target"
"github.com/mafredri/cdp/rpcc"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
)
var (
srv *httptest.Server
devtoolsSrv *httptest.Server
)
func init() {
srv = httptest.NewServer(root())
listen = srv.Listener.Addr().String()
devtoolsSrv = httptest.NewServer(mockDevtoolsMux())
defaultDevtoolsHost = devtoolsSrv.Listener.Addr().String()
}
func mockDevtoolsMux() http.Handler {
mux := http.NewServeMux()
version := func(w http.ResponseWriter, _ *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf(`{
"Browser": "Chrome/72.0.3601.0",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3601.0 Safari/537.36",
"V8-Version": "7.2.233",
"WebKit-Version": "537.36 (@cfede9db1d154de0468cb0538479f34c0755a0f4)",
"webSocketDebuggerUrl": "ws://%s/devtools/browser/b0b8a4fb-bb17-4359-9533-a8d9f3908bd8"
}`, defaultDevtoolsHost)))
}
mux.HandleFunc("/json/version", version)
listTargets := func(w http.ResponseWriter, _ *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf(`[ {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/one",
"id": "one",
"title": "Aerokube",
"type": "page",
"url": "https://www.aerokube.com/",
"webSocketDebuggerUrl": "ws://%s/devtools/page/one"
}, {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/two",
"id": "two",
"title": "Aerokube Selenoid",
"type": "page",
"url": "https://selenoid.aerokube.com/",
"webSocketDebuggerUrl": "ws://%s/devtools/page/two"
} ]`, defaultDevtoolsHost, defaultDevtoolsHost)))
}
mux.HandleFunc("/json/list", listTargets)
mux.HandleFunc("/json", listTargets)
mux.HandleFunc("/json/protocol", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Write([]byte("{}"))
})
upgrader := websocket.Upgrader{
CheckOrigin: func(_ *http.Request) bool {
return true
},
}
devtoolsEcho := func(w http.ResponseWriter, r *http.Request) {
//Echo request ID but omit Method
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
panic(err)
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
break
}
var req rpcc.Request
err = json.Unmarshal(message, &req)
if err != nil {
panic(err)
}
resp := rpcc.Response{
ID: req.ID,
}
if req.Method == "Target.getTargets" {
result := target.GetTargetsReply{
TargetInfos: []target.Info{
{TargetID: "one", Type: "page"},
{TargetID: "two", Type: "page"},
},
}
resBytes, _ := json.Marshal(result)
resp.Result = json.RawMessage(resBytes)
}
output, err := json.Marshal(resp)
if err != nil {
panic(err)
}
err = c.WriteMessage(mt, output)
if err != nil {
break
}
}
}
mux.HandleFunc("/devtools/browser/", devtoolsEcho)
mux.HandleFunc("/devtools/page/", devtoolsEcho)
return mux
}
func TestProtocol(t *testing.T) {
u := fmt.Sprintf("http://%s/json/protocol", srv.Listener.Addr().String())
resp, err := http.Get(u)
AssertThat(t, err, Is{nil})
AssertThat(t, resp, Code{200})
}
func TestDevtools(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
browserWs := fmt.Sprintf("ws://%s/", srv.Listener.Addr().String())
browserConn, err := rpcc.DialContext(ctx, browserWs)
AssertThat(t, err, Is{nil})
defer browserConn.Close()
browserClient := cdp.NewClient(browserConn)
targets, err := browserClient.Target.GetTargets(ctx)
AssertThat(t, err, Is{nil})
AssertThat(t, len(targets.TargetInfos), EqualTo{2})
defaultPageWs := fmt.Sprintf("ws://%s/page", srv.Listener.Addr().String())
defaultPageConn, err := rpcc.DialContext(ctx, defaultPageWs)
AssertThat(t, err, Is{nil})
defer defaultPageConn.Close()
defaultPageClient := cdp.NewClient(defaultPageConn)
err = defaultPageClient.Page.Enable(ctx)
AssertThat(t, err, Is{nil})
selectedPageWs := fmt.Sprintf("ws://%s/page/%s", srv.Listener.Addr().String(), targets.TargetInfos[1].TargetID)
selectedPageConn, err := rpcc.DialContext(ctx, selectedPageWs)
AssertThat(t, err, Is{nil})
defer selectedPageConn.Close()
selectedPageClient := cdp.NewClient(selectedPageConn)
err = selectedPageClient.Page.Enable(ctx)
AssertThat(t, err, Is{nil})
}
func TestDetectDevtoolsHost(t *testing.T) {
name, _ := ioutil.TempDir("", "devtools")
defer os.RemoveAll(name)
profilePath := filepath.Join(name, ".org.chromium.Chromium.deadbee")
os.MkdirAll(profilePath, os.ModePerm)
portFile := filepath.Join(profilePath, "DevToolsActivePort")
ioutil.WriteFile(portFile, []byte("12345\n/devtools/browser/6f37c7fe-a0a6-4346-a6e2-80c5da0687f0"), 0644)
AssertThat(t, detectDevtoolsHost(name), EqualTo{"127.0.0.1:12345"})
}
+12
View File
@@ -0,0 +1,12 @@
module github.com/aerokube/devtools
go 1.14
require (
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e
github.com/google/go-cmp v0.2.0 // indirect
github.com/gorilla/websocket v1.4.2
github.com/mafredri/cdp v0.23.4
golang.org/x/net v0.0.0-20190107210223-45ffb0cd1ba0 // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
)
+12
View File
@@ -0,0 +1,12 @@
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e h1:ogUKYFNcdYUIBSLibE4+EjbTJazoHr5JsWWx21Lpn8c=
github.com/aandryashin/matchers v0.0.0-20161126170413-435295ea180e/go.mod h1:cbmYNkm9xeQlNoWEPtOUcvNok2gSD7ErMnYkRW+eHi8=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mafredri/cdp v0.23.4 h1:ffp4qq6slfCL4rFWBDeRHapkLE776gER4tX5Z3LS8CY=
github.com/mafredri/cdp v0.23.4/go.mod h1:hgdiA0yp1uqhSaDOHJWPgXpMbh+LAfUdD9vbN2AM8gE=
golang.org/x/net v0.0.0-20190107210223-45ffb0cd1ba0 h1:1DW40AJQ7AP4nY6ORUGUdkpXyEC9W2GAXcOPaMZK0K8=
golang.org/x/net v0.0.0-20190107210223-45ffb0cd1ba0/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+23
View File
@@ -0,0 +1,23 @@
package main
import (
"flag"
"log"
"net/http"
)
var (
listen string
android bool
)
func init() {
flag.StringVar(&listen, "listen", ":7070", "Network address to accept connections")
flag.BoolVar(&android, "android", false, "Whether we need to forward Android Emulator devtools port")
}
func main() {
flag.Parse()
log.Printf("[INIT] [Listening on %s]", listen)
log.Fatal(http.ListenAndServe(listen, root()))
}
+93
View File
@@ -0,0 +1,93 @@
#!/bin/bash
SCREEN_RESOLUTION=${SCREEN_RESOLUTION:-"1920x1080x24"}
DISPLAY_NUM=99
export DISPLAY=":$DISPLAY_NUM"
VERBOSE=${VERBOSE:-""}
DRIVER_ARGS=${DRIVER_ARGS:-""}
if [ -n "$VERBOSE" ]; then
DRIVER_ARGS="$DRIVER_ARGS --verbose"
fi
clean() {
if [ -n "$FILESERVER_PID" ]; then
kill -TERM "$FILESERVER_PID"
fi
if [ -n "$XSELD_PID" ]; then
kill -TERM "$XSELD_PID"
fi
if [ -n "$XVFB_PID" ]; then
kill -TERM "$XVFB_PID"
fi
if [ -n "$DRIVER_PID" ]; then
kill -TERM "$DRIVER_PID"
fi
if [ -n "$X11VNC_PID" ]; then
kill -TERM "$X11VNC_PID"
fi
if [ -n "$DEVTOOLS_PID" ]; then
kill -TERM "$DEVTOOLS_PID"
fi
if [ -n "$PULSE_PID" ]; then
kill -TERM "$PULSE_PID"
fi
}
trap clean SIGINT SIGTERM
if env | grep -q ROOT_CA_; then
mkdir -p $HOME/.pki/nssdb
certutil -N --empty-password -d sql:$HOME/.pki/nssdb
for e in $(env | grep ROOT_CA_ | sed -e 's/=.*$//'); do
certname=$(echo -n $e | sed -e 's/ROOT_CA_//')
echo ${!e} | base64 -d >/tmp/cert.pem
certutil -A -n ${certname} -t "TC,C,T" -i /tmp/cert.pem -d sql:$HOME/.pki/nssdb
if cat tmp/cert.pem | grep -q "PRIVATE KEY"; then
PRIVATE_KEY_PASS=${PRIVATE_KEY_PASS:-\'\'}
openssl pkcs12 -export -in /tmp/cert.pem -clcerts -nodes -out /tmp/key.p12 -passout pass:${PRIVATE_KEY_PASS} -passin pass:${PRIVATE_KEY_PASS}
pk12util -d sql:$HOME/.pki/nssdb -i /tmp/key.p12 -W ${PRIVATE_KEY_PASS}
rm /tmp/key.p12
fi
rm /tmp/cert.pem
done
fi
/usr/bin/fileserver &
FILESERVER_PID=$!
/usr/bin/devtools &
DEVTOOLS_PID=$!
DISPLAY="$DISPLAY" /usr/bin/xseld &
XSELD_PID=$!
while ip addr | grep inet | grep -q tentative > /dev/null; do sleep 0.1; done
mkdir -p ~/pulse/.config/pulse
echo -n 'gIvST5iz2S0J1+JlXC1lD3HWvg61vDTV1xbmiGxZnjB6E3psXsjWUVQS4SRrch6rygQgtpw7qmghDFTaekt8qWiCjGvB0LNzQbvhfs1SFYDMakmIXuoqYoWFqTJ+GOXYByxpgCMylMKwpOoANEDePUCj36nwGaJNTNSjL8WBv+Bf3rJXqWnJ/43a0hUhmBBt28Dhiz6Yqowa83Y4iDRNJbxih6rB1vRNDKqRr/J9XJV+dOlM0dI+K6Vf5Ag+2LGZ3rc5sPVqgHgKK0mcNcsn+yCmO+XLQHD1K+QgL8RITs7nNeF1ikYPVgEYnc0CGzHTMvFR7JLgwL2gTXulCdwPbg=='| base64 -d>~/pulse/.config/pulse/cookie
HOME=$HOME/pulse pulseaudio --start --exit-idle-time=-1
HOME=$HOME/pulse pactl load-module module-native-protocol-tcp
PULSE_PID=$(ps --no-headers -C pulseaudio -o pid | sed -r 's/( )+//g')
/usr/bin/xvfb-run -l -n "$DISPLAY_NUM" -s "-ac -screen 0 $SCREEN_RESOLUTION -noreset -listen tcp" /usr/bin/fluxbox -display "$DISPLAY" -log /dev/null 2>/dev/null &
XVFB_PID=$!
retcode=1
until [ $retcode -eq 0 ]; do
DISPLAY="$DISPLAY" wmctrl -m >/dev/null 2>&1
retcode=$?
if [ $retcode -ne 0 ]; then
echo Waiting X server...
sleep 0.1
fi
done
if [ "$ENABLE_VNC" == "true" ]; then
x11vnc -display "$DISPLAY" -passwd selenoid -shared -forever -loop500 -rfbport 5900 -rfbportv6 5900 -logfile /dev/null &
X11VNC_PID=$!
fi
DISPLAY="$DISPLAY" /usr/bin/chromedriver --port=4444 --allowed-ips='' --allowed-origins='*' ${DRIVER_ARGS} &
DRIVER_PID=$!
wait
+62
View File
@@ -0,0 +1,62 @@
FROM browsers/base:7.3.6
ARG VERSION=noop
ARG PACKAGE=google-chrome-stable
ARG INSTALL_DIR=chrome
LABEL browser=$PACKAGE:$VERSION
RUN \
apt-get update && \
apt-get -y --no-install-recommends install gconf-service \
libasound2 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libfreetype6 \
libgcc1 \
libgconf-2-4 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
ca-certificates \
fonts-liberation \
libappindicator3-1 \
libnss3 \
lsb-base \
xdg-utils \
libcurl4 \
iproute2 \
curl && \
curl -O http://host.docker.internal:8080/google-chrome.deb && \
apt-get -y purge curl && \
dpkg -i google-chrome.deb && \
sed -i -e 's@exec -a "$0" "$HERE/chrome"@& --no-sandbox --disable-gpu@' /opt/google/$INSTALL_DIR/$PACKAGE && \
rm google-chrome.deb && \
chown root:root /opt/google/$INSTALL_DIR/chrome-sandbox && \
chmod 4755 /opt/google/$INSTALL_DIR/chrome-sandbox && \
google-chrome --version && \
rm -Rf /tmp/* && \
rm -Rf /var/lib/apt/lists/*
+12
View File
@@ -0,0 +1,12 @@
ARG VERSION
FROM selenoid/dev_edge:$VERSION
ENV DBUS_SESSION_BUS_ADDRESS=/dev/null
COPY msedgedriver /usr/bin/
COPY entrypoint.sh /
RUN chmod +x /usr/bin/msedgedriver
USER selenium
EXPOSE 4444
ENTRYPOINT ["/entrypoint.sh"]
+21
View File
@@ -0,0 +1,21 @@
FROM browsers/base:7.3.6
ARG VERSION
ARG PACKAGE=microsoft-edge-stable
ARG INSTALL_DIR=msedge
LABEL browser=$PACKAGE:$VERSION
RUN \
curl -s https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \
echo 'deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main' > /etc/apt/sources.list.d/microsoft-edge.list && \
apt-get update && \
apt-get -y --no-install-recommends install $PACKAGE=$VERSION && \
( \
sed -i -e 's@exec -a "$0" "$HERE/msedge"@& --no-sandbox --disable-gpu@' /opt/microsoft/$INSTALL_DIR/$PACKAGE || \
sed -i -e 's@exec -a "$0" "$HERE/msedge"@& --no-sandbox --disable-gpu@' /opt/microsoft/$INSTALL_DIR/microsoft-edge \
) && \
chown root:root /opt/microsoft/$INSTALL_DIR/msedge-sandbox && \
chmod 4755 /opt/microsoft/$INSTALL_DIR/msedge-sandbox && \
microsoft-edge --version && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
SCREEN_RESOLUTION=${SCREEN_RESOLUTION:-"1920x1080x24"}
DISPLAY_NUM=99
export DISPLAY=":$DISPLAY_NUM"
VERBOSE=${VERBOSE:-""}
DRIVER_ARGS=${DRIVER_ARGS:-""}
if [ -n "$VERBOSE" ]; then
DRIVER_ARGS="$DRIVER_ARGS --verbose"
fi
clean() {
if [ -n "$FILESERVER_PID" ]; then
kill -TERM "$FILESERVER_PID"
fi
if [ -n "$XSELD_PID" ]; then
kill -TERM "$XSELD_PID"
fi
if [ -n "$PULSE_PID" ]; then
kill -TERM "$PULSE_PID"
fi
if [ -n "$XVFB_PID" ]; then
kill -TERM "$XVFB_PID"
fi
if [ -n "$DRIVER_PID" ]; then
kill -TERM "$DRIVER_PID"
fi
if [ -n "$X11VNC_PID" ]; then
kill -TERM "$X11VNC_PID"
fi
}
trap clean SIGINT SIGTERM
if env | grep -q ROOT_CA_; then
mkdir -p $HOME/.pki/nssdb
certutil -N --empty-password -d sql:$HOME/.pki/nssdb
for e in $(env | grep ROOT_CA_ | sed -e 's/=.*$//'); do
certname=$(echo -n $e | sed -e 's/ROOT_CA_//')
echo ${!e} | base64 -d >/tmp/cert.pem
certutil -A -n ${certname} -t "TC,C,T" -i /tmp/cert.pem -d sql:$HOME/.pki/nssdb
if cat tmp/cert.pem | grep -q "PRIVATE KEY"; then
PRIVATE_KEY_PASS=${PRIVATE_KEY_PASS:-\'\'}
openssl pkcs12 -export -in /tmp/cert.pem -clcerts -nodes -out /tmp/key.p12 -passout pass:${PRIVATE_KEY_PASS} -passin pass:${PRIVATE_KEY_PASS}
pk12util -d sql:$HOME/.pki/nssdb -i /tmp/key.p12 -W ${PRIVATE_KEY_PASS}
rm /tmp/key.p12
fi
rm /tmp/cert.pem
done
fi
/usr/bin/fileserver &
FILESERVER_PID=$!
DISPLAY="$DISPLAY" /usr/bin/xseld &
XSELD_PID=$!
mkdir -p ~/pulse/.config/pulse
echo -n 'gIvST5iz2S0J1+JlXC1lD3HWvg61vDTV1xbmiGxZnjB6E3psXsjWUVQS4SRrch6rygQgtpw7qmghDFTaekt8qWiCjGvB0LNzQbvhfs1SFYDMakmIXuoqYoWFqTJ+GOXYByxpgCMylMKwpOoANEDePUCj36nwGaJNTNSjL8WBv+Bf3rJXqWnJ/43a0hUhmBBt28Dhiz6Yqowa83Y4iDRNJbxih6rB1vRNDKqRr/J9XJV+dOlM0dI+K6Vf5Ag+2LGZ3rc5sPVqgHgKK0mcNcsn+yCmO+XLQHD1K+QgL8RITs7nNeF1ikYPVgEYnc0CGzHTMvFR7JLgwL2gTXulCdwPbg=='| base64 -d>~/pulse/.config/pulse/cookie
HOME=$HOME/pulse pulseaudio --start --exit-idle-time=-1
HOME=$HOME/pulse pactl load-module module-native-protocol-tcp
PULSE_PID=$(ps --no-headers -C pulseaudio -o pid | sed -r 's/( )+//g')
/usr/bin/xvfb-run -l -n "$DISPLAY_NUM" -s "-ac -screen 0 $SCREEN_RESOLUTION -noreset -listen tcp" /usr/bin/fluxbox -display "$DISPLAY" -log /dev/null 2>/dev/null &
XVFB_PID=$!
retcode=1
until [ $retcode -eq 0 ]; do
DISPLAY="$DISPLAY" wmctrl -m >/dev/null 2>&1
retcode=$?
if [ $retcode -ne 0 ]; then
echo Waiting X server...
sleep 0.1
fi
done
if [ "$ENABLE_VNC" == "true" ]; then
x11vnc -display "$DISPLAY" -passwd selenoid -shared -forever -loop500 -rfbport 5900 -rfbportv6 5900 -logfile /dev/null &
X11VNC_PID=$!
fi
DISPLAY="$DISPLAY" /usr/bin/msedgedriver --port=4444 --allowed-ips='' ${DRIVER_ARGS} &
DRIVER_PID=$!
wait
+47
View File
@@ -0,0 +1,47 @@
FROM browsers/base:7.3.6
ARG VERSION=noop
ARG PACKAGE=microsoft-edge-stable
ARG INSTALL_DIR=msedge
LABEL browser=$PACKAGE:$VERSION
RUN \
apt-get update && \
apt-get -y --no-install-recommends install libatk-bridge2.0-0 \
libatomic1 \
libatspi2.0-0 \
libcairo-gobject2 \
libcolord2 \
libepoxy0 \
libgbm1 \
libgtk-3-0 \
libgtk-3-common \
liblcms2-2 \
librest-0.7-0 \
libsoup-gnome2.4-1 \
libwayland-client0 \
libwayland-cursor0 \
libwayland-egl1 \
libwayland-server0 \
libxkbcommon0 \
xdg-utils \
ca-certificates \
fonts-liberation \
libappindicator3-1 \
libnss3 \
lsb-base \
libcurl4 \
curl && \
curl -O http://host.docker.internal:8080/microsoft-edge.deb && \
apt-get -y purge curl && \
dpkg -i microsoft-edge.deb && \
( \
sed -i -e 's@exec -a "$0" "$HERE/msedge"@& --no-sandbox --disable-gpu@' /opt/microsoft/$INSTALL_DIR/$PACKAGE || \
sed -i -e 's@exec -a "$0" "$HERE/msedge"@& --no-sandbox --disable-gpu@' /opt/microsoft/$INSTALL_DIR/microsoft-edge \
) && \
rm microsoft-edge.deb && \
chown root:root /opt/microsoft/$INSTALL_DIR/msedge-sandbox && \
chmod 4755 /opt/microsoft/$INSTALL_DIR/msedge-sandbox && \
microsoft-edge --version && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
+20
View File
@@ -0,0 +1,20 @@
FROM browsers/base:7.3.6
ARG VERSION
ARG PACKAGE=firefox
ARG PPA
LABEL browser=$PACKAGE:$VERSION
RUN \
( [ "$PPA" != "" ] && \
apt-get update && \
apt-get install -y software-properties-common && \
apt-get update && \
add-apt-repository -y $PPA \
) || true && \
apt-get update && \
apt-get -y --no-install-recommends install iproute2 libavcodec58 $PACKAGE=$VERSION && \
( [ "$PACKAGE" != "firefox" ] && ln /usr/bin/$PACKAGE /usr/bin/firefox ) || true && \
firefox --version && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/*
+36
View File
@@ -0,0 +1,36 @@
FROM browsers/base:7.3.6
ARG VERSION=noop
ARG PACKAGE=firefox
ARG PPA=noop
LABEL browser=$PACKAGE:$VERSION
RUN \
( \
( \
curl -O http://host.docker.internal:8080/firefox.deb && \
apt-get update && \
apt-get -y --no-install-recommends install iproute2 libavcodec58 && \
apt-get -y install libgtk-3-0 libstartup-notification0 libdbus-glib-1-2 \
) || \
( \
curl -O http://host.docker.internal:8080/firefox.deb && \
dpkg --add-architecture i386 && \
apt-get update && \
apt-get -y --no-install-recommends install iproute2 libavcodec58 && \
apt-get -y install libc6:i386 libncurses5:i386 libstdc++6:i386 libgtk-3-0:i386 libasound2:i386 libdbus-glib-1-2:i386 libxt6:i386 libatomic1:i386 libcairo-gobject2:i386 libstartup-notification0:i386 libx11-xcb1:i386 && \
mkdir flashplayer && \
cd flashplayer && \
curl -o flashplayer.tar.gz https://fpdownload.adobe.com/pub/flashplayer/pdc/32.0.0.387/flash_player_ppapi_linux.i386.tar.gz && \
tar xvzf flashplayer.tar.gz && \
cp libpepflashplayer.so /usr/lib/flashplugin-installer/libflashplayer.so && \
cd .. && \
rm -Rf flashplayer \
) \
) && \
dpkg -i firefox.deb && \
( [ "$PACKAGE" != "firefox" ] && ln /usr/bin/$PACKAGE /usr/bin/firefox ) || true && \
rm firefox.deb && \
firefox --version && \
rm -Rf /tmp/* && rm -Rf /var/lib/apt/lists/* && rm -f firefox*.deb
+16
View File
@@ -0,0 +1,16 @@
ARG VERSION
FROM selenoid/dev_firefox:$VERSION
RUN \
apt-get update && \
apt-get install -y openjdk-8-jre-headless && \
apt-get clean && \
rm -Rf /tmp/*
COPY selenium-server-standalone.jar /usr/share/selenium/
COPY entrypoint.sh /
USER selenium
EXPOSE 4444
ENTRYPOINT ["/entrypoint.sh"]

Some files were not shown because too many files have changed in this diff Show More