2

I'm using Selenium with Python to generate inputs to credit card fields on a website. When you try send_keys to the field it always returns this error. I used different webdrivers (Chrome, Edge, Firefox) with the same effect. The error pops up before any input shows up in the field.

from selenium import webdriver
browser = webdriver.Chrome(r"C:\Users\m234234\Downloads\chromedriver_win32.exe")
...
...
Element3 = browser.find_element(By.ID, 'scp_cardPage_csc_input')
Element3.send_keys('231')

The element that is trying to access:

<input autocomplete="off" maxlength="4" type="text" class="scp_text_input" name="csc" aria-required="true" aria-label="Security Code" aria-describedby="scp_cardPage_csc_error" id="scp_cardPage_csc_input" value="" size="5" onkeypress="return checkNum(event)">

Full error below

WebDriverException: unknown error: Runtime.callFunctionOn threw exception: TypeError: JSON.stringify is not a function
    at buildError (<anonymous>:323:18)
  (Session info: chrome=113.0.5672.126)

Update

Once the script reaches this payment page (it's a third party payment page), it throws this same error when it tries to interact with the page in any way, even if I just try to retrieve current_url

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
wwhitman
  • 81
  • 8
  • 1
    friend, your question is incomplete unless you add the URL or some sample – Ajeet Verma May 18 '23 at 09:10
  • can't add the url for security reasons – wwhitman May 18 '23 at 09:16
  • 1
    Where did you initializes the `browser` that you are using in `element3` ? – Akzy May 18 '23 at 09:27
  • Edited with the browser initialization. The rest are a long list of steps that run successfully to get to the payments page (sorry can't share this bit) – wwhitman May 18 '23 at 09:37
  • Are you sure the error is coming from this piece of code? The error says :JSON.stringify is not a function which reference not clear in this code. Can you search "JSON.stringify " in your project? – QualityMatters May 18 '23 at 10:06
  • afaik JSON.stringify is a javascript function, is not anywhere in the python script (the python is very stripped down, the rest of the code is just to get to this payment page) – wwhitman May 18 '23 at 13:11
  • @wwhitman did you ever find an answer? I have the same problem using Selenium in .Net and Chrome = 113.0.5672.127 – McCrockett May 22 '23 at 23:21
  • never got an answer, but I suspect the JSON.stringify error is just incidental and that is a server side security issue – wwhitman May 24 '23 at 13:05

2 Answers2

3

json.stringify()

The json.stringify() static method is a built-in function in JavaScript that converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.

The json.stringify() method takes one, two or three parameters, such as:

  • Value: The JavaScript object or value to be converted to a JSON string.
  • Replacer (optional): It is a function that can be used to modify the values and properties of the object that is being converted.
  • space (optional): A string or number that can be used to insert white space or line break characters into the output JSON string for readability purposes.

Root cause

The error...

TypeError: JSON.stringify is not a function

can occur if the program is trying to use the JSON.stringify() method on a non-object or when the method is not defined for a particular object. As an example:

var person = "undetectedSelenium";
var jsonString = JSON.stringify(name);
console.log(jsonString);

In the above code block we are trying to convert a string value to a JSON string using the JSON.stringify() method. JSON.stringify() method only works with JavaScript objects and arrays. Calling this method on a simple string value will result in this error.

To fix this error, we can wrap the string value in an object or an array as follows:

var person = "undetectedSelenium";
var jsonObject = { "name": person };
var jsonString = JSON.stringify(jsonObject);
console.log(jsonString);

Some common causes

Some of the most common causes of this error are:

  • Using an Older Version of JavaScript
  • Incorrect Syntax
  • Overwriting the JSON Object
  • Incorrect Data Type

This usecase

In this usecase it seems the script is unable to locate the element using the following line of code:

Element3 = browser.find_element(By.ID, 'scp_cardPage_csc_input')

effectively Element3 remains a non-object. Hence the error is raised.


Possible fixes

There are a couple of approaches to fix up this error as follows:

  • Verify if the json.stringify() method is defined and incase it isn't define a new function that converts an object to a JSON string. This solution is useful in older browsers that doesn't support the JSON object natively.

    if (typeof JSON.stringify !== 'function') {
      JSON.stringify = function(obj) {
        // code to convert obj to a JSON string
      };
    }
    
  • Check if the obj variable is a valid JSON object before calling the json.stringify() method which can prevent the type error from being thrown when trying to call the method on a non-object.

    if (typeof obj === 'object' && obj !== null) {
      var jsonString = JSON.stringify(obj);
    }
    
  • Using a third-party JSON library, like MyJSON to convert the object to a JSON string.

    var jsonString = MyJSON.stringify(obj);
    
  • Using a try-catch block to prevent the program from crashing if the error occurs:

    try {
      var jsonString = JSON.stringify(obj);
    } catch (e) {
      console.error('Error: ' + e.message);
    }
    

Road ahead

This issue isn't related to Selenium Python Client but an issue with the sourcecode of ChromeDriver and needs to be addressed by the ChromeDriver team.


tl; dr

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
2

Hoping you already solved this issue, actually I am also facing same issue from last week and keep tracing your question, and just today, I got a solution, so I will share this.

I've invesitigate into webdriver.exe by transform it to txt file, then I find before version 113, webdriver are using jsonSerialize method upon Json object webdriver version 112. and after version 112, they are using JSON.stringify to serialize Json object webdriver version above 112

but on very few websites, they may miss JSON.stringify method, if you open the devtool and execute this command in console, you will see a error, just like console error

so bacially what you should do is run a snippet of JS code to define the JSON.stringify method before you perform any find element or send key action, for my solution is using (JavascriptExecutor)webdrive.execute() to do this, the JS code you need just like following, also you can find this in JSON-js/json2.js

if (typeof JSON !== "object") {
    JSON = {};
}

(function () {
    "use strict";

    var rx_one = /^[\],:{}\s]*$/;
    var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
    var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
    var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

    function f(n) {
        // Format integers to have at least two digits.
        return (n < 10)
            ? "0" + n
            : n;
    }

    function this_value() {
        return this.valueOf();
    }

    if (typeof Date.prototype.toJSON !== "function") {

        Date.prototype.toJSON = function () {

            return isFinite(this.valueOf())
                ? (
                    this.getUTCFullYear()
                    + "-"
                    + f(this.getUTCMonth() + 1)
                    + "-"
                    + f(this.getUTCDate())
                    + "T"
                    + f(this.getUTCHours())
                    + ":"
                    + f(this.getUTCMinutes())
                    + ":"
                    + f(this.getUTCSeconds())
                    + "Z"
                )
                : null;
        };

        Boolean.prototype.toJSON = this_value;
        Number.prototype.toJSON = this_value;
        String.prototype.toJSON = this_value;
    }

    var gap;
    var indent;
    var meta;
    var rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        rx_escapable.lastIndex = 0;
        return rx_escapable.test(string)
            ? "\"" + string.replace(rx_escapable, function (a) {
                var c = meta[a];
                return typeof c === "string"
                    ? c
                    : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
            }) + "\""
            : "\"" + string + "\"";
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i;          // The loop counter.
        var k;          // The member key.
        var v;          // The member value.
        var length;
        var mind = gap;
        var partial;
        var value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (
            value
            && typeof value === "object"
            && typeof value.toJSON === "function"
        ) {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === "function") {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case "string":
            return quote(value);

        case "number":

// JSON numbers must be finite. Encode non-finite numbers as null.

            return (isFinite(value))
                ? String(value)
                : "null";

        case "boolean":
        case "null":

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce "null". The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is "object", we might be dealing with an object or an array or
// null.

        case "object":

// Due to a specification blunder in ECMAScript, typeof null is "object",
// so watch out for that case.

            if (!value) {
                return "null";
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === "[object Array]") {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || "null";
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0
                    ? "[]"
                    : gap
                        ? (
                            "[\n"
                            + gap
                            + partial.join(",\n" + gap)
                            + "\n"
                            + mind
                            + "]"
                        )
                        : "[" + partial.join(",") + "]";
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === "object") {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    if (typeof rep[i] === "string") {
                        k = rep[i];
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (
                                (gap)
                                    ? ": "
                                    : ":"
                            ) + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.prototype.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (
                                (gap)
                                    ? ": "
                                    : ":"
                            ) + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0
                ? "{}"
                : gap
                    ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}"
                    : "{" + partial.join(",") + "}";
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== "function") {
        meta = {    // table of character substitutions
            "\b": "\\b",
            "\t": "\\t",
            "\n": "\\n",
            "\f": "\\f",
            "\r": "\\r",
            "\"": "\\\"",
            "\\": "\\\\"
        };
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = "";
            indent = "";

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === "number") {
                for (i = 0; i < space; i += 1) {
                    indent += " ";
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === "string") {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== "function" && (
                typeof replacer !== "object"
                || typeof replacer.length !== "number"
            )) {
                throw new Error("JSON.stringify");
            }

// Make a fake root object containing our value under the key of "".
// Return the result of stringifying the value.

            return str("", {"": value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== "function") {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k;
                var v;
                var value = holder[key];
                if (value && typeof value === "object") {
                    for (k in value) {
                        if (Object.prototype.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            rx_dangerous.lastIndex = 0;
            if (rx_dangerous.test(text)) {
                text = text.replace(rx_dangerous, function (a) {
                    return (
                        "\\u"
                        + ("0000" + a.charCodeAt(0).toString(16)).slice(-4)
                    );
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with "()" and "new"
// because they can cause invocation, and "=" because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we
// replace all simple value tokens with "]" characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or "]" or
// "," or ":" or "{" or "}". If that is so, then the text is safe for eval.

            if (
                rx_one.test(
                    text
                        .replace(rx_two, "@")
                        .replace(rx_three, "]")
                        .replace(rx_four, "")
                )
            ) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The "{" operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval("(" + text + ")");

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return (typeof reviver === "function")
                    ? walk({"": j}, "")
                    : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError("JSON.parse");
        };
    }
}());
  • 1
    Thanks, also for another Issue 4481, it's the same reason, may Json.stringify has been overwritten in some websites, whose reture object do not have hasOwnProperty method, that's why we can see so many TypeError issue like this after chrome driver 113 released, because from version 112, webdriver start to use build in method JSON.stringify. – Citi-Vincent Jun 21 '23 at 15:57
  • Hi All, Do we have concrete answer/fix to this problem. – Balaji Singh .Y Jul 02 '23 at 18:23
  • @Balaji yes, wo got a concrete answer for this already, see [link](https://bugs.chromium.org/p/chromedriver/issues/detail?id=4481) – Citi-Vincent Jul 17 '23 at 02:41
  • Citi -Vincent : Thanks for the info. – Balaji Singh .Y Jul 26 '23 at 15:01