Socket
Socket
Sign inDemoInstall

protractor

Package Overview
Dependencies
Maintainers
1
Versions
103
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

protractor - npm Package Compare versions

Comparing version 0.12.1 to 0.13.0

45

CHANGELOG.md

@@ -0,1 +1,46 @@

# 0.13.0
_Note: Major version 0 releases are for initial development, and backwards compatible changes may be introduced at any time._
## Features
- ([ce5f494](https://github.com/angular/protractor/commit/ce5f494289c3750b84c6783339a14342a1b74f3d)) feat(element): element.all now has 'first' and 'last' methods
- ([ef61662](https://github.com/angular/protractor/commit/ef6166232186b3385769f63430819a722052cc44)) feat(runner): allow bypassing the selenium standalone server if running only chrome
Using the config option `chromeOnly` now enables running ChromeDriver directly,
without going through the Selenium Standalone. The chromedriver binary should be
available in your PATH, or should be specified with the config option
`chromeDriver`.
- ([76c094a](https://github.com/angular/protractor/commit/76c094a3fa69511b0311011b0ef2c7343b8e655b)) feat(getLocationAbsUrl) - allows current url to be obtained on IE (and Chrome/Firefox)
- ([6a1c918](https://github.com/angular/protractor/commit/6a1c91848858453d0af712588b51c0bdaa0c9445)) feat(runner): add error message for bad jar path
- ([98bce7e](https://github.com/angular/protractor/commit/98bce7e2ac1e659faf2d8727e1fda210b796525e)) feat(locators): add the ability to add custom element locators with by.addLocator
Custom locators can now be added using by.addLocator(name, script), where
script is a self-contained snippet to be executed on the browser which returns
an array of elements. Closes #236.
- ([c7bcc20](https://github.com/angular/protractor/commit/c7bcc20c07416237f69f7934d257b5ba5bfe8c1f)) chore(angular): update to angular 1.2
## Bug Fixes
- ([a24eeee](https://github.com/angular/protractor/commit/a24eeee4f08e973ffcecd107b6610ce1c2c5e3f6)) fix(runner): do not error out if only one spec pattern does not match any files
Previously, the runner would throw an error if any one of the spec patterns did not
match any files. Now it logs a warning in that case, and errors out only if there
are no found files in any spec patterns. Closes #260
- ([f3b3fdb](https://github.com/angular/protractor/commit/f3b3fdbcbc8fe4f3c5915ef0f6eb7c89e339a62e)) fix(element): fix an error where all.then() wasn't calling callbacks.
Closes #267
- ([137d804](https://github.com/angular/protractor/commit/137d8040778215fd841654d3ca465b71f8719ea5)) fix(jasminewd): patched matcher should understand 'not'
Closes #139.
# 0.12.1

@@ -2,0 +47,0 @@

14

docs/debugging.md

@@ -18,3 +18,3 @@ Debugging Protractor Tests

```
protractor debugging/failure_conf.js
protractor debugging/failureConf.js
```

@@ -45,3 +45,3 @@

// Run this statement before the line which fails. If protractor is run
// with the debugger (protractor debug debugging/conf.js), the test
// with the debugger (protractor debug <...>), the test
// will pause after loading the webpage but before trying to find the

@@ -59,3 +59,3 @@ // element.

```
protractor debug debugging/failure_conf.js
protractor debug debugging/failureConf.js
```

@@ -79,5 +79,5 @@

```javascript
// In the browser console
> window.clientSideScripts.findInput('user.name');
// Should return the input element with model 'user.name'.
// In the browser console (e.g. from Chrome Dev Tools)
> window.clientSideScripts.findInputs('username');
// Should return the input element with model 'username'.
```

@@ -146,3 +146,3 @@

```
protractor debugging/timeout_conf.js
protractor debugging/timeoutConf.js
```

@@ -149,0 +149,0 @@

@@ -43,2 +43,9 @@ FAQ

Angular can't be found on my page
---------------------------------
Protractor supports angular 1.0.6/1.1.4 and higher - check that your version of Angular is upgraded.
The `angular` variable is expected to be available in the global context. Try opening chrome devtools or firefox and see if `angular` is defined.
How do I deal with my log-in page?

@@ -45,0 +52,0 @@ ----------------------------------

@@ -23,3 +23,3 @@ Getting Started

- WebDriver commands are scheduled on a control flow and return promises, not
primitive values. See the [control flow doc](/control-flow.md) for more
primitive values. See the [control flow doc](/docs/control-flow.md) for more
info.

@@ -26,0 +26,0 @@ - To run tests, WebDriverJS needs to talk to a selenium standalone server

@@ -70,4 +70,5 @@ /**

* resolve to the actual value being tested.
* @param {boolean} not Whether this is being called with 'not' active.
*/
function wrapMatcher(matcher, actualPromise) {
function wrapMatcher(matcher, actualPromise, not) {
return function(expected) {

@@ -77,6 +78,14 @@ actualPromise.then(function(actual) {

expected.then(function(exp) {
expect(actual)[matcher](exp);
if (not) {
expect(actual).not[matcher](exp)
} else {
expect(actual)[matcher](exp);
}
});
} else {
expect(actual)[matcher](expected);
if (not) {
expect(actual).not[matcher](expected)
} else {
expect(actual)[matcher](expected);
}
}

@@ -94,3 +103,3 @@ });

function promiseMatchers(actualPromise) {
var promises = {};
var promises = {not: {}};
var env = jasmine.getEnv();

@@ -100,3 +109,4 @@ var matchersClass = env.currentSpec.matchersClass || env.matchersClass;

for (matcher in matchersClass.prototype) {
promises[matcher] = wrapMatcher(matcher, actualPromise);
promises[matcher] = wrapMatcher(matcher, actualPromise, false);
promises.not[matcher] = wrapMatcher(matcher, actualPromise, true);
};

@@ -103,0 +113,0 @@

@@ -114,2 +114,16 @@ require('../index.js');

describe('not', function() {
it('should still pass normal synchronous tests', function() {
expect(4).not.toEqual(5);
});
it('should compare a promise to a primitive', function() {
expect(fakeDriver.getValueA()).not.toEqual('b');
});
it('should compare a promise to a promise', function() {
expect(fakeDriver.getValueA()).not.toEqual(fakeDriver.getValueB());
});
});
it('should throw an error with a WebElement actual value', function() {

@@ -116,0 +130,0 @@ var webElement = new webdriver.WebElement(fakeDriver, 'idstring');

@@ -31,28 +31,2 @@ /**

/**
* Find an element in the page by their angular binding.
*
* arguments[0] {Element} The scope of the search.
* arguments[1] {string} The binding, e.g. {{cat.name}}.
*
* @return {WebElement} The element containing the binding.
*/
clientSideScripts.findBinding = function() {
var using = arguments[0] || document;
var binding = arguments[1];
var bindings = using.getElementsByClassName('ng-binding');
var matches = [];
for (var i = 0; i < bindings.length; ++i) {
var elemData = angular.element(bindings[i]).data();
if (!elemData || !elemData.$binding) {
continue;
}
var bindingName = elemData.$binding[0].exp || elemData.$binding;
if (bindingName.indexOf(binding) != -1) {
matches.push(bindings[i]);
}
}
return matches[0]; // We can only return one with webdriver.findElement.
};
/**
* Find a list of elements in the page by their angular binding.

@@ -84,3 +58,4 @@ *

/**
* Find a row within an ng-repeat.
* Find an array of elements matching a row within an ng-repeat. Always returns
* an array of only one element.
*

@@ -91,32 +66,2 @@ * arguments[0] {Element} The scope of the search.

*
* @return {Element} The row element.
*/
clientSideScripts.findRepeaterRow = function() {
var using = arguments[0] || document;
var repeater = arguments[1];
var index = arguments[2];
var rows = [];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
for (var p = 0; p < prefixes.length; ++p) {
var attr = prefixes[p] + 'repeat';
var repeatElems = using.querySelectorAll('[' + attr + ']');
attr = attr.replace(/\\/g, '');
for (var i = 0; i < repeatElems.length; ++i) {
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
rows.push(repeatElems[i]);
}
}
}
return rows[index];
};
/**
* Find an array of elements matching a row within an ng-repeat. There
* will only be one element, but this is necessary for isElementPresent.
*
* arguments[0] {Element} The scope of the search.
* arguments[1] {string} The text of the repeater, e.g. 'cat in cats'.
* arguments[2] {number} The row index.
*
* @return {Array.<Element>} An array of a single element, the row of the

@@ -180,3 +125,3 @@ * repeater.

*
* @return {Element} The element.
* @return {Array.<Element>} The element in an array.
*/

@@ -221,4 +166,3 @@ clientSideScripts.findRepeaterElement = function() {

}
// We can only return one with webdriver.findElement.
return matches[0];
return matches;
};

@@ -277,23 +221,2 @@

/**
* Find input element by model name.
*
* arguments[0] {Element} The scope of the search.
* arguments[1] {string} The model name.
*
* @return {Element} The first matching input element.
*/
clientSideScripts.findInput = function() {
var using = arguments[0] || document;
var model = arguments[1];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
for (var p = 0; p < prefixes.length; ++p) {
var selector = 'input[' + prefixes[p] + 'model="' + model + '"]';
var inputs = using.querySelectorAll(selector);
if (inputs.length) {
return inputs[0];
}
}
};
/**
* Find an input elements by model name.

@@ -305,3 +228,3 @@ *

* @return {Array.<Element>} The matching input elements.
*/
*/
clientSideScripts.findInputs = function() {

@@ -320,23 +243,2 @@ var using = arguments[0] || document;

/**
* Find an select element by model name.
*
* arguments[0] {Element} The scope of the search.
* arguments[1] {string} The model name.
*
* @return {Element} The first matching select element.
*/
clientSideScripts.findSelect = function() {
var using = arguments[0] || document;
var model = arguments[1];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
for (var p = 0; p < prefixes.length; ++p) {
var selector = 'select[' + prefixes[p] + 'model="' + model + '"]';
var inputs = using.querySelectorAll(selector);
if (inputs.length) {
return inputs[0];
}
}
};
/**

@@ -349,3 +251,3 @@ * Find multiple select elements by model name.

* @return {Array.<Element>} The matching select elements.
*/
*/
clientSideScripts.findSelects = function() {

@@ -365,24 +267,2 @@ var using = arguments[0] || document;

/**
* Find an selected option element by model name.
*
* arguments[0] {Element} The scope of the search.
* arguments[1] {string} The model name.
*
* @return {Element} The first matching input element.
*/
clientSideScripts.findSelectedOption = function() {
var using = arguments[0] || document;
var model = arguments[1];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
for (var p = 0; p < prefixes.length; ++p) {
var selector =
'select[' + prefixes[p] + 'model="' + model + '"] option:checked';
var inputs = using.querySelectorAll(selector);
if (inputs.length) {
return inputs[0];
}
}
};
/**
* Find selected option elements by model name.

@@ -409,3 +289,3 @@ *

/**
* Find a textarea element by model name.
* Find textarea elements by model name.
*

@@ -415,7 +295,8 @@ * arguments[0] {Element} The scope of the search.

*
* @return {Element} The first matching textarea element.
* @return {Array.<Element>} An array of matching textarea elements.
*/
clientSideScripts.findTextarea = function() {
clientSideScripts.findTextareas = function() {
var using = arguments[0] || document;
var model = arguments[1];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];

@@ -426,3 +307,3 @@ for (var p = 0; p < prefixes.length; ++p) {

if (textareas.length) {
return textareas[0];
return textareas;
}

@@ -477,1 +358,11 @@ }

};
/**
* Return the current url using $location.absUrl().
*
* arguments[0] {string} The selector housing an ng-app
*/
clientSideScripts.getLocationAbsUrl = function() {
var el = document.querySelector(arguments[0]);
return angular.element(el).injector().get('$location').absUrl();
};

@@ -23,20 +23,37 @@ var util = require('util');

/**
* Add a locator to this instance of ProtractorBy. This locator can then be
* used with element(by.<name>(<args>)).
*
* @param {string} name
* @param {function|string} script A script to be run in the context of
* the browser. This script will be passed an array of arguments
* that begins with the element scoping the search, and then
* contains any args passed into the locator. It should return
* an array of elements.
*/
ProtractorBy.prototype.addLocator = function(name, script) {
this[name] = function(varArgs) {
return {
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(script), using, varArgs);
},
message: 'by.' + name + '("' + varArgs + '")'
}
};
};
/**
* Usage:
* <span>{{status}}</span>
* var status = element(by.binding('{{status}}'));
*
* Note: This ignores parent element restrictions if called with
* WebElement.findElement.
*/
ProtractorBy.prototype.binding = function(bindingDescriptor) {
return {
findOverride: function(driver, using) {
return driver.findElement(webdriver.By.js(clientSideScripts.findBinding),
using, bindingDescriptor);
},
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findBindings),
using, bindingDescriptor);
}
},
message: 'by.binding("' + bindingDescriptor + '")'
};

@@ -52,10 +69,7 @@ };

return {
findOverride: function(driver, using) {
return driver.findElement(
webdriver.By.js(clientSideScripts.findSelect), using, model);
},
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findSelects), using, model);
}
},
message: 'by.select("' + model + '")'
};

@@ -71,10 +85,7 @@ };

return {
findOverride: function(driver, using) {
return driver.findElement(
webdriver.By.js(clientSideScripts.findSelectedOption), using, model);
},
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findSelectedOptions), using, model);
}
},
message: 'by.selectedOption("' + model + '")'
};

@@ -91,10 +102,7 @@ };

return {
findOverride: function(driver, using) {
return driver.findElement(
webdriver.By.js(clientSideScripts.findInput), using, model);
},
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findInputs), using, model);
}
},
message: 'by.input("' + model + '")'
};

@@ -110,10 +118,7 @@ };

return {
findOverride: function(driver, using) {
return driver.findElement(
webdriver.By.js(clientSideScripts.findInput), using, model);
},
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findInputs), using, model);
}
},
message: 'by.model("' + model + '")'
};

@@ -129,6 +134,7 @@ };

return {
findOverride: function(driver, using) {
return driver.findElement(
webdriver.By.js(clientSideScripts.findTextarea), using, model);
}
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findTextareas), using, model);
},
message: 'by.textarea("' + model + '")'
};

@@ -158,3 +164,3 @@ };

return {
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(

@@ -164,10 +170,6 @@ webdriver.By.js(clientSideScripts.findAllRepeaterRows),

},
message: 'by.repeater("' + repeatDescriptor + '")',
row: function(index) {
return {
findOverride: function(driver, using) {
return driver.findElement(
webdriver.By.js(clientSideScripts.findRepeaterRow),
using, repeatDescriptor, index);
},
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(

@@ -177,9 +179,12 @@ webdriver.By.js(clientSideScripts.findRepeaterRows),

},
message: 'by.repeater(' + repeatDescriptor + '").row("' + index + '")"',
column: function(binding) {
return {
findOverride: function(driver, using) {
return driver.findElement(
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findRepeaterElement),
using, repeatDescriptor, index, binding);
}
},
message: 'by.repeater("' + repeatDescriptor + '").row("' + index +
'").column("' + binding + '")'
};

@@ -191,3 +196,3 @@ }

return {
findArrayOverride: function(driver, using) {
findElementsOverride: function(driver, using) {
return driver.findElements(

@@ -197,9 +202,13 @@ webdriver.By.js(clientSideScripts.findRepeaterColumn),

},
message: 'by.repeater("' + repeatDescriptor + '").column("' + binding +
'")',
row: function(index) {
return {
findOverride: function(driver, using) {
return driver.findElement(
findElementsOverride: function(driver, using) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findRepeaterElement),
using, repeatDescriptor, index, binding);
}
},
message: 'by.repeater("' + repeatDescriptor + '").column("' +
binding + '").row("' + index + '")'
};

@@ -206,0 +215,0 @@ }

@@ -23,2 +23,7 @@ var url = require('url');

/**
* @type {ProtractorBy}
*/
exports.By = new ProtractorBy();
/**
* Mix a function from one object onto another. The function will still be

@@ -108,6 +113,23 @@ * called in the context of the original object.

elementArrayFinder.then = function() {
return ptor.findElements(locator);
elementArrayFinder.first = function() {
var id = ptor.findElements(locator).then(function(arr) {
if (!arr.length) {
throw new Error('No element found using locator: ' + locator.message);
}
return arr[0];
});
return new webdriver.WebElement(ptor.driver, id);
};
elementArrayFinder.last = function() {
var id = ptor.findElements(locator).then(function(arr) {
return arr[arr.length - 1];
});
return new webdriver.WebElement(ptor.driver, id);
};
elementArrayFinder.then = function(fn) {
return ptor.findElements(locator).then(fn);
};
return elementArrayFinder;

@@ -271,2 +293,3 @@ }

* @param {webdriver.WebElement} element
* @return {webdriver.WebElement} the wrapped web element.
*/

@@ -310,4 +333,4 @@ Protractor.prototype.wrapWebElement = function(element) {

var found;
if (locator.findOverride) {
found = locator.findOverride(element.getDriver(), element);
if (locator.findElementsOverride) {
found = thisPtor.findElementsOverrideHelper_(element, locator);
} else {

@@ -342,4 +365,4 @@ found = originalFindElement.apply(element, arguments);

var found;
if (locator.findArrayOverride) {
found = locator.findArrayOverride(element.getDriver(), element);
if (locator.findElementsOverride) {
found = locator.findElementsOverride(element.getDriver(), element);
} else {

@@ -365,4 +388,4 @@ found = originalFindElements.apply(element, arguments);

thisPtor.waitForAngular();
if (locator.findArrayOverride) {
return locator.findArrayOverride(element.getDriver(), element).
if (locator.findElementsOverride) {
return locator.findElementsOverride(element.getDriver(), element).
then(function (arr) {

@@ -403,4 +426,4 @@ return !!arr.length;

if (locator.findOverride) {
found = locator.findOverride(this.driver);
if (locator.findElementsOverride) {
found = this.findElementsOverrideHelper_(null, locator);
} else {

@@ -423,4 +446,4 @@ found = this.driver.findElement(locator, varArgs);

if (locator.findArrayOverride) {
found = locator.findArrayOverride(this.driver);
if (locator.findElementsOverride) {
found = locator.findElementsOverride(this.driver);
} else {

@@ -447,4 +470,4 @@ found = this.driver.findElements(locator, varArgs);

this.waitForAngular();
if (locatorOrElement.findArrayOverride) {
return locatorOrElement.findArrayOverride(this.driver).then(function(arr) {
if (locatorOrElement.findElementsOverride) {
return locatorOrElement.findElementsOverride(this.driver).then(function(arr) {
return !!arr.length;

@@ -546,2 +569,10 @@ });

/**
* Returns the current absolute url from AngularJS.
*/
Protractor.prototype.getLocationAbsUrl = function() {
this.waitForAngular();
return this.driver.executeScript(clientSideScripts.getLocationAbsUrl, this.rootEl);
};
/**
* Pauses the test and injects some helper functions into the browser, so that

@@ -581,2 +612,31 @@ * debugging may be done in the browser console.

/**
* Builds a single web element from a locator with a findElementsOverride.
* Throws an error if an element is not found, and issues a warning
* if more than one element is described by the selector.
*
* @private
* @param {webdriver.WebElement} using A WebElement to scope the find,
* or null.
* @param {webdriver.Locator} locator
* @return {webdriver.WebElement}
*/
Protractor.prototype.findElementsOverrideHelper_ = function(using, locator) {
// We need to return a WebElement, so we construct one using a promise
// which will resolve to a WebElement.
return new webdriver.WebElement(
this.driver,
locator.findElementsOverride(this.driver, using).then(function(arr) {
if (!arr.length) {
throw new Error('No element found using locator: ' + locator.message);
}
if (arr.length > 1) {
util.puts('warning: more than one element found for locator ' +
locator.message +
'- you may need to be more specific');
}
return arr[0];
}));
};
/**
* Create a new instance of Protractor by wrapping a webdriver instance.

@@ -612,7 +672,1 @@ *

}
/**
* @type {ProtractorBy}
*/
exports.By = new ProtractorBy();

@@ -6,2 +6,3 @@ var util = require('util');

var remote = require('selenium-webdriver/remote');
var chrome = require('selenium-webdriver/chrome');
var minijn = require('minijasminenode');

@@ -114,3 +115,6 @@ var protractor = require('./protractor.js');

if (config.seleniumAddress) {
if (config.chromeOnly) {
util.puts('Using ChromeDriver directly...');
deferred.fulfill(null);
} else if (config.seleniumAddress) {
util.puts('Using the selenium server at ' + config.seleniumAddress);

@@ -142,2 +146,8 @@ deferred.fulfill(config.seleniumAddress);

}
if (config.seleniumServerJar && !fs.existsSync(config.seleniumServerJar)) {
throw new Error('there\'s no selenium server jar at the specified location.'+
'Do you have the correct version?');
}
server = new remote.SeleniumServer(config.seleniumServerJar, {

@@ -181,3 +191,3 @@ args: config.seleniumArgs,

if (!matches.length) {
throw new Error('Test file ' + specs[i] + ' did not match any files.');
util.puts('Warning: pattern ' + specs[i] + ' did not match any files.');
}

@@ -188,2 +198,5 @@ for (var j = 0; j < matches.length; ++j) {

}
if (!resolvedSpecs.length) {
throw new Error('Spec patterns did not match any files.');
}

@@ -194,6 +207,13 @@ minijn.addSpecs(resolvedSpecs);

var runDeferred = webdriver.promise.defer();
driver = new webdriver.Builder().
usingServer(config.seleniumAddress).
withCapabilities(config.capabilities).build();
if (config.chromeOnly) {
var service = new chrome.ServiceBuilder(config.chromeDriver).build();
driver = chrome.createDriver(
new webdriver.Capabilities(config.capabilities), service);
} else {
driver = new webdriver.Builder().
usingServer(config.seleniumAddress).
withCapabilities(config.capabilities).build();
}
driver.getSession().then(function(session) {

@@ -200,0 +220,0 @@ driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);

@@ -39,3 +39,3 @@ {

"license" : "MIT",
"version": "0.12.1"
"version": "0.13.0"
}

@@ -12,2 +12,6 @@ // A reference configuration file.

// 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.
//
// If the chromeOnly option is specified, no Selenium server will be started,
// and chromeDriver will be used directly (from the location specified in
// chromeDriver)

@@ -24,2 +28,5 @@ // The location of the selenium standalone server .jar file.

chromeDriver: './selenium/chromedriver',
// If true, only chromedriver will be started, not a standalone selenium.
// Tests for browsers other than chrome will not run.
chromeOnly: false,
// Additional command line options to pass to selenium. For example,

@@ -26,0 +33,0 @@ // if you need to change the browser timeout, use

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc